Características del Wizard MQL5 que debe conocer (Parte 74): Uso de patrones de Ichimoku y del ADX-Wilder con aprendizaje supervisado
Introducción
En el artículo anterior, analizamos la combinación de indicadores Ichimoku y ADX-Wilder como una herramienta complementaria para identificar niveles de soporte y resistencia y para el análisis de tendencias. Como de costumbre, probamos estos patrones en un Asesor Experto creado mediante un asistente y analizamos 10 patrones de señal diferentes. Para este par de indicadores, la mayoría de ellos pudieron avanzar de forma rentable durante 1 año, tras haber realizado pruebas/optimizaciones el año anterior. Sin embargo, hubo 3 que no lo hicieron de manera satisfactoria, y estos fueron el patrón 0, el patrón 1 y el patrón 5. Por lo tanto, complementamos ese artículo examinando si el aprendizaje supervisado puede marcar la diferencia en su desempeño. Nuestro enfoque consiste en reconstruir las señales de cada uno de estos patrones como un vector de entrada simple en una red neuronal, convirtiendo esencialmente la red neuronal en un filtro adicional para la señal.
La Red
Hemos optado por un modelo de aprendizaje supervisado basado en una red que utiliza un núcleo de mezcla espectral. Esto viene definido por la siguiente fórmula:

Donde:
- τ \tau : Diferencia de tiempo entre dos puntos.
- Q : Número de componentes espectrales.
- wi : Peso del i-ésimo componente (amplitud).
- li: Escala de longitud del i-ésimo componente (controla la atenuación).
- fi: Frecuencia del i-ésimo componente (frecuencia de oscilación).
Estamos utilizando este núcleo de mezcla espectral para definir una capa de entrada en una red neuronal sencilla, seguida de capas lineales de PyTorch. El tipo de capa que estamos creando y utilizando es una capa de mezcla espectral. Implementamos esta capa y las redes de regresión que le siguen en Python, tal y como se indica a continuación:
class DeepSpectralMixtureRegressor(nn.Module): def __init__(self, input_dim=2, num_components=96): super().__init__() self.smk = DeepSpectralMixtureLayer(input_dim, num_components) feature_dim = input_dim * num_components # 2 * 96 = 192 self.head = nn.Sequential( nn.Linear(feature_dim, 1024), nn.ReLU(), nn.Linear(1024, 512), nn.ReLU(), nn.Linear(512, 256), nn.ReLU(), nn.Linear(256, 128), nn.ReLU(), nn.Linear(128, 1) # <--- Removed sigmoid! ) def forward(self, x): features = self.smk(x) output = self.head(features) return output
Algunas investigaciones sugieren que el uso de nuestro núcleo de mezcla especial en la entrada sirve para extraer características de los datos de entrada, mientras que el cabezal de regresión de las capas posteriores en la salida resulta adecuado en los casos en que el modelo es un regresor, tal y como se muestra con nuestro uso de la función sigmoide. En nuestra implementación del núcleo de mezcla espectral, la primera capa se encarga de modelar los datos de forma periódica, mientras que las capas de regresión o «cabeza» aprenden las frecuencias, las varianzas y los pesos de la red. Nuestro enfoque de implementación es el más adecuado para tareas de regresión con patrones oscilatorios en los datos de entrada. Además, combina la flexibilidad de las redes neuronales con los conceptos de los procesos gaussianos.
Nuestra red se llama «DeepSpectralMixtureRegressor». Además, estamos adoptando una arquitectura modular que consta de dos partes. Extracción de características y regresión. La primera parte del proceso de extracción de características se lleva a cabo específicamente mediante la función «DeepSpectralMixtureLayer», cuyo código se muestra a continuación:
class DeepSpectralMixtureLayer(nn.Module): def __init__(self, input_dim, num_components): super().__init__() self.num_components = num_components hidden = 512 # Wider hidden layers # Go deeper with 4 layers def make_subnet(): return nn.Sequential( nn.Linear(input_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, num_components), ) self.freq_net = make_subnet() self.var_net = nn.Sequential(*make_subnet(), nn.Softplus()) self.weight_net = nn.Sequential(*make_subnet(), nn.Softplus()) def forward(self, x): mu = self.freq_net(x) v = self.var_net(x) w = self.weight_net(x) expanded = x.unsqueeze(2) mu = mu.unsqueeze(1) v = v.unsqueeze(1) w = w.unsqueeze(1) x_cos = torch.cos(2 * np.pi * expanded * mu) x_exp = torch.exp(-v * expanded**2) features = w * x_exp * x_cos return features.flatten(start_dim=1)
La función anterior aprende frecuencias, varianzas y ponderaciones mediante el uso de redes neuronales. Esto garantiza un resultado positivo, dado que para las varianzas y los pesos se utiliza la activación «soft-plus». El cabezal de regresión asigna las características extraídas a una única salida con activación sigmoide para la regresión acotada o la clasificación binaria. El principal argumento a favor de esta arquitectura es su adaptabilidad, ya que el modelo se adapta a diversos patrones de datos. Esto puede hacer que resulte adecuado para tareas como la previsión de series temporales, sobre todo si la serie contiene componentes periódicos. De este modo, se aprovechan tanto la red neuronal como los enfoques basados en kernels.
Wilson et al. (2013) señalan que los núcleos de mezcla espectral pueden implementarse en bibliotecas como GPyTorch como funciones de covarianza para su uso en el proceso gaussiano de modelización de patrones periódicos, siempre que representen la densidad espectral como una mezcla de componentes. Así pues, nuestro enfoque adapta este concepto a una red neuronal mediante la parametrización de los componentes del núcleo —frecuencia, varianza y pesos— a través de redes neuronales. Esto permite una extracción de características basada en el aprendizaje y en los datos, lo que puede resultar especialmente relevante para problemas como la predicción de series temporales, en los que los datos presentan patrones periódicos u oscilatorios. Así lo destacó Sebastian Callh en su entrada de blog sobre los núcleos de mezcla espectral. Parte de la inspiración para aplicar el núcleo tal y como lo hacemos proviene también de esta documentación de GPyTorch.
En resumen, en lugar de tener un conjunto fijo de parámetros, nuestro modelo utiliza redes neuronales para aprender frecuencias, que denominamos «mu», varianzas, que denominamos «v», y pesos, que simbolizamos con «w». Este enfoque permite adaptarse a diversos patrones de datos. La primera capa de extracción de características calcula las características utilizando la fórmula «w * exp(-v * x^2) * cos(2πμx)», que capta la periodicidad mediante la modulación coseno con la suavidad del decaimiento exponencial. El diseño modular, en el que la extracción de características se realiza en una capa mediante DeepSpectralMixtureLayer y el módulo de regresión constituye un componente independiente, aumenta la flexibilidad. Por su parte, el núcleo de regresión es una red profunda y totalmente conectada que asigna las características a una única salida con la ayuda de la función de activación sigmoide. Y esto, como ya se ha mencionado, lo hace adecuado para la regresión acotada o la clasificación binaria.
Analicemos en profundidad el código anterior para ver cómo se implementa todo lo que acabamos de describir. Nuestro código comienza con las importaciones estándar de PyTorch y las bibliotecas relacionadas. Estas importaciones proporcionan herramientas para operaciones con tensores, la construcción de redes neuronales, la optimización, el tratamiento de datos y la exportación en formato ONNX del modelo entrenado para su inferencia en plataformas de terceros. Esto es fundamental para poder crear, entrenar e implementar el modelo. Esto, como cabría esperar, es fundamental no solo para el desarrollo basado en PyTorch, sino también para el aprendizaje automático en general. A modo de orientación, es importante garantizar la compatibilidad entre las versiones de PyTorch y ONNX para que la carga de datos durante el entrenamiento sea eficiente.
A continuación, procedemos a configurar la inicialización de nuestra «DeepSpectralMixtureLayer». Esto define tres redes neuronales: la red de frecuencias, la red de varianzas y la red de pesos. Cada una de las redes asigna la entrada al número de salidas de los componentes, utilizando la función de activación «Soft plus», que garantiza que las varianzas y los pesos se mantengan positivos, tal y como exige la formulación del núcleo. Esto es importante porque estas redes parametrizan por separado el núcleo de mezcla espectral para permitir la extracción de características basada en los datos mediante procesos gaussianos. El parámetro «num_components» controla la «riqueza de características». Este parámetro, «num_components», se puede ajustar en función de la complejidad de la tarea. El uso de la activación «soft plus» evita los valores negativos que pueden provocar inestabilidad numérica.
A continuación, definimos nuestra función de propagación para la red. Esta función calcula las características de la mezcla espectral mediante la fórmula mencionada anteriormente en el texto. Reestructura las entradas y los parámetros para que puedan transmitirse al módulo de regresión. La aplicación de las funciones coseno y exponencial suaviza el resultado de las capas de regresión. Esta transformación del núcleo imita el núcleo de mezcla espectral. La forma de la variable de entrada «x» debe seguir el formato (tamaño_del_lote, dimensión_de_entrada). Además, las comprobaciones de estabilidad numérica en «exp» pueden requerir la limitación de la varianza v. El parámetro «num_components» puede ajustarse a la dimensionalidad deseada de las características.
Una vez abordada la capa de extracción de características, como se ha visto anteriormente, podemos pasar ahora al regresor, la clase que se invoca al definir el modelo. Su objetivo principal es combinar la capa de mezcla espectral descrita anteriormente con una cabeza totalmente conectada. Este cabezal asigna las características del núcleo a una única salida en el intervalo 0-1 mediante la función sigmoide. La estructura se puede ajustar en función de la complejidad de la tarea aumentando el tamaño o el número de capas. La función sigmoide puede eliminarse en tareas de regresión sin límites. Por fin también tenemos una función directa, igual que en la función de capa espectral anterior. Hace pasar la entrada por la capa de mezcla espectral para extraer características y, a continuación, por la cabeza para obtener la salida final. Esta función de propagación define el proceso completo de propagación, lo que permite el entrenamiento y la inferencia de extremo a extremo.
El tamaño de la entrada «x» debe coincidir con el de la dimensión de entrada. Una función de pérdida adecuada, como la BCE, puede resultar adecuada, dado que se utiliza la función sigmoide como función de activación. El parámetro «num-components» influye considerablemente en el rendimiento de la red, y su valor determina el número total de componentes espectrales. Los valores más altos aumentan la expresividad, pero conllevan el riesgo de un sobreajuste. Un enfoque sensato puede consistir en empezar con un valor de 64 y luego ajustarlo en función de la complejidad de los datos. Es fundamental vigilar que no se produzca un sobreajuste durante el entrenamiento, dada la naturaleza excesivamente expresiva de la capa de mezcla espectral. La regularización y la detención temprana pueden ayudar a mitigar esto.
El análisis de los parámetros aprendidos mu, v y w puede ayudar a comprender los patrones periódicos que se observan en estos ejemplos de GPyTorch. En comparación con los modelos típicos de procesos gaussianos, nuestro enfoque carece de estimaciones de incertidumbre. Se podrían añadir medidas adicionales que cuantifiquen este aspecto. Por último, un paso hacia adelante puede suponer una gran carga computacional, sobre todo cuando el parámetro «num-components» es muy elevado o se utilizan entradas de alta dimensión. Este es un aspecto adicional que hay que tener en cuenta a la hora de ajustar y optimizar este modelo.
Implementaciones de indicadores
De los patrones de señal que probamos en el último artículo, la mayoría fueron capaces de avanzar hacia delante con bastante soltura, salvo el patrón 0, el patrón 1 y el patrón 5. En resumen, hemos probado y optimizado 10 patrones de señal en el par GBP/USD para el año 2023. Utilizamos el marco temporal de 30 minutos, a diferencia de nuestro habitual de 4 horas, ya que las señales del Ichimoku no suelen ser tan frecuentes en marcos temporales más amplios. Como de costumbre, nuestra proyección se centró en el año 2024. Aunque 7 de los 10 patrones lograron generar beneficios en 2025, nuestras pruebas solo abarcaron un periodo de dos años, por lo que siempre se recomienda al lector que realice un análisis más exhaustivo y realice pruebas con series históricas de precios más amplias antes de aplicar lo aquí presentado a un entorno de negociación real.
Una vez expuestos estos antecedentes, veamos cómo se implementan en Python los indicadores Ichimoku y ADX de Wilder, así como sus funciones de extracción de características para los patrones 0, 1 y 5.
Ichimoku en Python
Implementamos la función Ichimoku en Python de la siguiente manera:
def Ichimoku(df, tenkan_period: int = 9, kijun_period: int = 26, senkou_span_b_period: int = 52, displacement: int = 26) -> pd.DataFrame: """ Calculate Ichimoku Kinko Hyo components and append them to the input DataFrame. The components are: - Tenkan-sen (Conversion Line) - Kijun-sen (Base Line) - Senkou Span A (Leading Span A) - Senkou Span B (Leading Span B) - Chikou Span (Lagging Span) Args: df (pd.DataFrame): DataFrame with 'high', 'low', and 'close' columns. tenkan_period (int): Lookback period for Tenkan-sen (default 9). kijun_period (int): Lookback period for Kijun-sen (default 26). senkou_span_b_period (int): Lookback period for Senkou Span B (default 52). displacement (int): Forward displacement of Senkou spans and backward shift of Chikou span (default 26). Returns: pd.DataFrame: Input DataFrame with added Ichimoku columns. """ # Input validation required_cols = {'high', 'low', 'close'} if not required_cols.issubset(df.columns): raise ValueError("DataFrame must contain 'high', 'low', and 'close' columns") if not all(p > 0 for p in [tenkan_period, kijun_period, senkou_span_b_period, displacement]): raise ValueError("All period values must be positive integers") result_df = df.copy() # Tenkan-sen (Conversion Line) high_tenkan = result_df['high'].rolling(window=tenkan_period).max() low_tenkan = result_df['low'].rolling(window=tenkan_period).min() result_df['Tenkan_sen'] = (high_tenkan + low_tenkan) / 2 # Kijun-sen (Base Line) high_kijun = result_df['high'].rolling(window=kijun_period).max() low_kijun = result_df['low'].rolling(window=kijun_period).min() result_df['Kijun_sen'] = (high_kijun + low_kijun) / 2 # Senkou Span A (Leading Span A) result_df['Senkou_Span_A'] = ((result_df['Tenkan_sen'] + result_df['Kijun_sen']) / 2).shift(displacement) # Senkou Span B (Leading Span B) high_span_b = result_df['high'].rolling(window=senkou_span_b_period).max() low_span_b = result_df['low'].rolling(window=senkou_span_b_period).min() result_df['Senkou_Span_B'] = ((high_span_b + low_span_b) / 2).shift(displacement) # Chikou Span (Lagging Span) result_df['Chikou_Span'] = result_df['close'].shift(-displacement) return result_df
Nuestra función de código anterior calcula los componentes del indicador y los añade al marco de datos de entrada con períodos predeterminados de 9 para el Tenkan-sen, 26 para el Kijun-sen, 26 para el desplazamiento y 52 para el Senkou span B. La primera línea de nuestro código tiene como objetivo validar los datos de entrada. De este modo se garantiza que el marco de datos contenga las columnas de precios necesarias y que los parámetros de período sean positivos, con el fin de evitar errores de cálculo. Este paso es fundamental para garantizar la integridad de los datos y evitar errores en tiempo de ejecución. Nuestra función indicadora debe ejecutarse con datos de tiempo de ejecución válidos. Siempre es recomendable validar los datos de entrada antes de procesarlos, para gestionar adecuadamente los datos que faltan o los parámetros no válidos.
Una vez realizada la validación, procedemos a calcular el Tenkan-sen. Nuestro código calcula la media entre el máximo más alto y el mínimo más bajo registrados durante el último «período tenkan». Normalmente, utilizamos un período de 9, que sirve como indicador de tendencia a corto plazo. Este indicador ayuda a señalar posibles cambios de tendencia al cruzar la línea Kijun-sen, lo que puede resultar útil a la hora de tomar decisiones de trading a corto plazo, ya que refleja la evolución reciente de los precios. Se pueden realizar ajustes en el «período Tenkan» para conseguir la sensibilidad deseada respecto a la evolución de los precios a corto plazo. Los periodos más cortos presentarán una mayor sensibilidad, mientras que los más largos la tendrán menor.
A continuación, calculamos el margen del Kijun-sen. Al igual que el Tenkan-sen, se calcula sobre un periodo más largo, normalmente de 26. Esto constituye un indicador de tendencia a medio plazo. El Kijun-sen es importante porque confirma la dirección de la tendencia y actúa como nivel de soporte o resistencia. Esto suele aportar una visión ligeramente más estable de los mercados en comparación con el Tenkan-sen. Resulta útil en el análisis a medio plazo y, de hecho, los cruces con la línea Tenkan-sen pueden servir como señales de negociación.
A continuación, calculamos la línea Senkou A. Se trata, sencillamente, de la media entre la línea Tenkan-sen y la línea Kijun-sen, desplazada hacia adelante en el periodo de desplazamiento. Este desplazamiento suele ser de 26. Esta zona de amortiguación constituye uno de los límites de la nube de Kumo. Es fundamental, ya que muestra los futuros niveles de soporte y resistencia, lo que ofrece una perspectiva prospectiva del análisis de tendencias, tal y como se explica aquí. Es importante vigilar la posición relativa de la nube respecto al precio: si el precio se sitúa por encima de ella, esto indica una perspectiva alcista, mientras que si se sitúa por debajo, la perspectiva sería bajista.
A continuación se calcula el Senkou Span B; para ello, simplemente se calcula la media entre el máximo más alto y el mínimo más bajo de un periodo que suele ser de 52, y se desplaza hacia adelante, formando el segundo límite de la nube Kumo. El Senkou B ofrece una visión a más largo plazo de los niveles de soporte y resistencia y facilita la identificación de tendencias, especialmente en marcos temporales más amplios. A modo de orientación, el grosor de la nube —la distancia entre Senkou A y Senkou B— puede servir como indicador de la volatilidad. Por lo tanto, unas nubes más densas pueden indicar un soporte o una resistencia más fuertes.
Por último, calculamos el último indicador, el Chikou Span. Este valor de referencia no es más que el precio de cierre desplazado hacia atrás en el período de desplazamiento —que suele ser de 26— con el fin de comparar los precios actuales con los precios anteriores. Por lo tanto, sirve para confirmar si los precios actuales se sitúan por encima o por debajo de los precios anteriores, lo cual resulta útil para confirmar o validar una tendencia. Por lo tanto, el objetivo principal del Chikou es la confirmación, lo que a menudo requiere disponer de datos históricos suficientes, dado el uso de desplazamientos y de largos periodos de promediado.
ADX-Wilder en Python
Una vez definido el Ichimoku en Python, pasamos ahora al ADX de Wilder. Esta función calcula el ADX utilizando el método de Wilder. Al igual que con el Ichimoku, añadimos al marco de datos de precios de entrada unos márgenes adicionales de ADX, +DI y -DI. Este indicador utiliza un período predeterminado de 14, que podría ajustarse para alinearlo con el Ichimoku o para lograr una mayor sensibilidad al precio; no obstante, utilizamos este valor predeterminado de 14. Lo implementamos en Python de la siguiente manera:
def ADX_Wilder(df, period: int = 14) -> pd.DataFrame: """ Calculate the Average Directional Index (ADX) using Wilder's method and append the ADX, +DI, and -DI columns to the input DataFrame. ADX measures trend strength, while +DI and -DI measure trend direction. Args: df (pd.DataFrame): DataFrame with 'high', 'low', and 'close' columns. period (int): The lookback period to use. Default is 14. Returns: pd.DataFrame: Input DataFrame with 'ADX', '+DI', and '-DI' columns. """ if not all(col in df.columns for col in ['high', 'low', 'close']): raise ValueError("DataFrame must contain 'high', 'low', and 'close' columns") if period <= 0: raise ValueError("Period must be a positive integer") result_df = df.copy() # Calculate directional movements up_move = result_df['high'].diff() down_move = result_df['low'].diff() plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0) minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0) # Calculate True Range (TR) high_low = result_df['high'] - result_df['low'] high_close = np.abs(result_df['high'] - result_df['close'].shift(1)) low_close = np.abs(result_df['low'] - result_df['close'].shift(1)) tr = np.maximum(high_low, np.maximum(high_close, low_close)) # Apply Wilder's smoothing atr = pd.Series(tr).rolling(window=period).mean() plus_di = 100 * pd.Series(plus_dm).rolling(window=period).sum() / atr minus_di = 100 * pd.Series(minus_dm).rolling(window=period).sum() / atr dx = 100 * np.abs(plus_di - minus_di) / (plus_di + minus_di) adx = dx.rolling(window=period).mean() result_df['+DI'] = plus_di result_df['-DI'] = minus_di result_df['ADX'] = adx return result_df
Lo primero que hacemos, nada más empezar, es validar los datos de entrada de la función. Esto garantiza, como siempre, que se incluyan las columnas de precios necesarias y que el período del indicador también sea válido, con el fin de evitar errores de cálculo. Todo ello es fundamental para la integridad de los datos y para operar con datos válidos. Asimismo, los casos de datos que faltan deben tratarse adecuadamente. Tras la validación, lo siguiente que abordamos son los cálculos del movimiento direccional. Estas son las diferencias entre los máximos y los mínimos que determinan los movimientos del DM positivo y del DM negativo. Indican la intensidad de las tendencias alcistas y bajistas, información que resulta clave para evaluar y analizar la dirección de las tendencias. Es importante garantizar que se disponga de datos históricos suficientes para poder calcular con precisión las diferencias direccionales. También conviene comprobar que no haya lagunas en los datos.
A continuación, procedemos a calcular las distancias reales. Estos valores se calculan como el mayor de los siguientes: el máximo actual menos el mínimo, el máximo menos el cierre anterior o el mínimo menos el cierre anterior. Se utilizan los valores absolutos de estas diferencias en aras de la coherencia. Este rango normaliza los movimientos direccionales, permitiéndolos comparar entre distintos activos, de acuerdo con los requisitos de cálculo del ADX. Es fundamental asegurarse de que no haya datos que falten en el marco de datos de precios de entrada para evitar errores derivados de valores «NaN» en los cálculos.
A continuación, procedemos a calcular el rango verdadero suavizado utilizando una media móvil. Además, suavizamos los búferes «plus DM» y «minus DM», normalizando los indicadores direccionales positivos y negativos de modo que se escalen a 100 para obtener una representación porcentual. El índice direccional, DX, un indicador de la fuerza de la tendencia, será, por tanto, la diferencia absoluta entre el DM positivo y el DM negativo, normalizada por su suma. Por último, el indicador principal, el ADX, será un promedio del DX a lo largo del periodo del indicador, que en nuestro caso es de 14. Este resultado final sirve, por tanto, para medir la intensidad de una tendencia predominante.
A modo de resumen del último artículo, el ADX ofrece valores de intensidad de la tendencia en un rango de 0 a 100, y los valores superiores a 25 suelen interpretarse como indicativos de una tendencia fuerte. Por otro lado, los valores inferiores a 20 se interpretan como un indicio de tendencias débiles o incipientes. El índice direccional positivo y el índice direccional negativo cuantifican la intensidad de las tendencias alcistas y bajistas, respectivamente, y la diferencia entre ambos puede servir como señal de entrada en el mercado. El período utilizado de 14 también se puede ajustar a la sensibilidad deseada; los valores más bajos dan lugar a una detección más temprana de la señal y de los puntos fractales, mientras que los períodos más largos resultarían menos volátiles.
Al aplicar el núcleo de mezcla espectral a través de una red regresiva, tal y como definimos tras la introducción, tomamos nuestras entradas de estos dos indicadores en forma de un vector simple de dos dimensiones. En algunos artículos anteriores, hemos analizado y explorado la opción de aumentar el tamaño del vector de entrada de nuestras redes de aprendizaje supervisado mediante la descomposición de las distintas señales de cada patrón en un valor booleano, 0 o 1. Esto proporcionó mejores resultados en el entrenamiento y la optimización, que, sin embargo, no se reflejaron en el forward walking. Por lo tanto, en este momento lo que combinamos en un único valor booleano son las señales y los patrones de cada indicador, y no sus partes. El indicador bidimensional combina, en cada dimensión, las señales de dos indicadores. La primera señal se centra exclusivamente en confirmar una señal alcista, mientras que la segunda lo hace con una señal bajista.
Característica 0
Estamos aplicando las tres características con peor rendimiento a nuestra red formada por la característica 0, la característica 1 y la característica 5. Implementamos la función «feature-0» en Python de la siguiente manera:;
def feature_0(df): """ """ feature = np.zeros((len(df), 2)) feature[:, 0] = ((df['close'].shift(1) < df['Senkou_Span_A'].shift(1)) & (df['close'] > df['Senkou_Span_A']) & (df['ADX'] >= 25.0)).astype(int) feature[:, 1] = ((df['close'].shift(1) > df['Senkou_Span_A'].shift(1)) & (df['close'] < df['Senkou_Span_A']) & (df['ADX'] >= 25.0)).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
Nuestra función comienza creando una matriz NumPy vacía. Este conjunto de características tiene un tamaño que se ajusta al del marco de datos de entrada en cuanto al número de filas, aunque se le han asignado dos columnas. Esto permite empezar de cero a la hora de generar señales y garantiza que ningún valor residual afecte al resultado. A continuación, asignamos el valor de la primera columna de este marco de datos, que sirve para comprobar la señal alcista. Según las condiciones que expusimos en el último artículo, se asigna un valor de 1 a cada fila de esta columna de la matriz NumPy cuando el cierre anterior está por debajo del Senkou Span A, el cierre actual está por encima del Senkou Span A actual y el ADX es de al menos 25. La detección de un cruce alcista, en el que el precio se sitúa por encima del Senkou Span A, suele indicar una tendencia alcista. El hecho de que el ADX sea de al menos 25 garantiza que la señal se produzca durante una tendencia fuerte, lo que reduce los falsos positivos en mercados con tendencias débiles.
Esta asignación, que utiliza matrices de NumPy, implica que las asignaciones se realizan en varias filas a la vez. Una de las principales razones por las que desarrollar y entrenar modelos en Python es tan decisivo. A continuación, asignamos el valor de la columna «bajista» a todas las filas de la matriz. Aquí, establecemos el valor de la columna, en cada fila correspondiente, en 1 si el cierre anterior está por encima del Senkou Span A anterior, el cierre actual está por debajo del Senkou Span A actual y el búfer principal del ADX es de al menos 25. Detecta el cruce bajista, lo que indica la posibilidad de una tendencia bajista. El filtro ADX garantiza que la fuerza de la tendencia respalde la señal.
Una vez asignados los valores alcistas y bajistas, forzamos a que las dos primeras filas de nuestra matriz de características sean cero, ya que no tienen valores y, por lo tanto, contienen un NaN. Pudimos entrenar nuestra red solo con este patrón, lo exportamos como «74_0.onnx» y lo importamos a una clase de señal personalizada de un asesor experto de MQL5. En el último artículo, este patrón concreto no logró generar un avance significativo; sin embargo, cuando lo probamos ahora utilizando la red como filtro adicional para las señales de entrada del indicador, obtenemos el siguiente informe:


Parece que la suerte está cambiando; sin embargo, esto se debe únicamente a las posiciones largas abiertas y, como siempre, nuestras pruebas tienen un alcance de datos muy limitado, por lo que solo tienen carácter exploratorio. Una señal de compra para la característica 0 se ve así en un gráfico:

Característica 1
Otra característica o patrón que tuvo un rendimiento pésimo en el último artículo fue la característica 1. Lo implementamos en Python de la siguiente manera:
def feature_1(df): """ """ feature = np.zeros((len(df), 2)) feature[:, 0] = ((df['Tenkan_sen'].shift(1) < df['Kijun_sen'].shift(1)) & (df['Tenkan_sen'] > df['Kijun_sen']) & (df['ADX'] >= 20.0)).astype(int) feature[:, 1] = ((df['Tenkan_sen'].shift(1) > df['Kijun_sen'].shift(1)) & (df['Tenkan_sen'] < df['Kijun_sen']) & (df['ADX'] >= 20.0)).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
Inicializamos de nuevo nuestra matriz de salida objetivo, también denominada «feature», con ceros y le asignamos un tamaño que se ajuste al del marco de datos de entrada. Se asignan dos columnas por cada punto de datos de una fila para registrar las señales alcistas y bajistas. En la primera columna, asignamos un 1 a una señal alcista confirmada, si el Tenkan-sen anterior se encuentra por debajo del Kijun-sen anterior, y el Tenkan-sen actual se sitúa ahora por encima del Kijun-sen actual, y el ADX es de al menos 20. Esta señal detecta el cruce alcista entre el Tenkan-sen y el Kijun-sen. Se trata de una señal a medio plazo que anticipa el inicio de tendencias importantes, por lo que el ADX no tiene por qué estar en 25, sino que puede situarse en 20.
La señal bajista, introducida en la segunda columna de cada fila de la matriz de NumPy, se establece en 1 cuando las condiciones son opuestas a las de nuestra señal alcista. Si el Tenkan-sen anterior estaba por encima del Kijun-sen y ahora el Tenkan-sen actual está por debajo del Kijun-sen actual, y el ADX es de al menos 20 y está al alza, entonces tenemos una señal bajista. Nuestro enfoque exige que se cumplan varios criterios antes de que se asigne un valor distinto de cero. Se trata de un enfoque estricto que contrasta claramente con la alternativa de utilizar matrices de mayor tamaño que toman las señales de los indicadores individuales como bits, y con las entradas a nuestra red a las que nos hemos referido anteriormente. Concluimos asignando ceros a las dos primeras filas de nuestra matriz NumPy, ya que hemos utilizado la comparación de desplazamiento y algunos puntos de datos tienen valores NaN en lugar de datos indicadores. El informe de la prueba del patrón 1 en el par GBP/USD, en el gráfico de 30 minutos y abarcando un periodo de dos años —tanto la fase de entrenamiento como la de prueba—, es el siguiente:


Nuestra red, que se superpone al procesamiento de señales del último artículo a modo de filtro, parece volver a marcar la diferencia en el rendimiento del Expert Advisor creado con el asistente. Un patrón alcista en un gráfico de barras puede presentarse de la siguiente manera para la característica 1:

Característica 5
El patrón final que estamos volviendo a analizar es el sexto, el patrón 5. En Python, lo implementamos de la siguiente manera:
def feature_5(df): """ """ feature = np.zeros((len(df), 2)) feature[:, 0] = ((df['close'].shift(2) > df['close'].shift(1)) & (df['close'].shift(1) < df['close']) & (df['close'].shift(2) > df['Tenkan_sen'].shift(2)) & (df['close'] > df['Tenkan_sen']) & (df['close'].shift(1) <= df['Tenkan_sen'].shift(1)) & (df['+DI'] > df['-DI']) & (df['ADX'] >= 25.0)).astype(int) feature[:, 1] = ((df['close'].shift(2) < df['close'].shift(1)) & (df['close'].shift(1) > df['close']) & (df['close'].shift(2) < df['Tenkan_sen'].shift(2)) & (df['close'] < df['Tenkan_sen']) & (df['close'].shift(1) >= df['Tenkan_sen'].shift(1)) & (df['+DI'] < df['-DI']) & (df['ADX'] >= 25.0)).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
Nuestro formato es similar al del patrón 0 y al del patrón 1 anteriores. De esta forma, inicializamos y definimos el tamaño de nuestra matriz NumPy de salida. Para asignar un 1 a la primera columna, lo que confirma la señal alcista, es necesario que el precio de cierre rebote y describa un giro en U sobre la línea Tenkan-sen. Además, el DM positivo debe situarse por encima del DM negativo, con el ADX en 25 o más. Esta evolución del precio suele indicar una continuación de la tendencia, y, antes de dar por hecho que así es, es necesario tener en cuenta tanto el tamaño relativo de los intervalos de amortiguación del movimiento direccional (con signo) del ADX como sus valores.
A la segunda columna se le asigna un 1 para sustituir el 0 predeterminado si se produce un desvío del precio respecto a la línea Tenkan-sen desde abajo, de modo que el cierre actual se sitúe por debajo de ella, y el ADX sea también de al menos 25, con un movimiento direccional negativo superior al positivo como confirmación final de la señal. Concluimos poniendo a cero las dos primeras filas de nuestra matriz de características para tener en cuenta los cálculos de desplazamiento que dan como resultado valores NaN, dada la naturaleza terminal de los datos de entrada. También hemos probado este patrón en condiciones similares a las de los otros dos patrones anteriores, y hemos obtenido el siguiente informe.


Nuestro modelo es rentable, aunque la evolución del valor de las acciones es muy irregular. Por lo tanto, hay motivos para complementar el aprendizaje automático con señales de indicadores típicos. Aunque nuestros resultados no son ideales y las pruebas también son limitadas, se podría argumentar que complementar las señales de los indicadores con una red de kernel de mezcla espectral puede servir como un filtro adicional para afinar la entrada en las operaciones y mejorar el rendimiento.
Conclusión
Hemos estudiado el uso de una red neuronal basada en el núcleo de mezcla espectral para ajustar con precisión las señales de trading sin procesar de la combinación de indicadores Ichimoku y ADX-Wilder. La estrategia especial que hemos adoptado se ha mostrado prometedora y, sin duda, ha supuesto una mejora en el rendimiento con respecto a lo que veíamos en el último artículo, donde las operaciones se realizaban únicamente basándose en las señales del indicador. Los resultados que aquí presentamos, como siempre, requieren un análisis más detallado por parte del lector antes de que puedan adoptarse o incorporarse a los sistemas de negociación existentes. El concepto de incorporación, especialmente durante las pruebas, es algo que permiten los Expert Advisors creados con el Asistente de MQL5, ya que durante su creación se pueden seleccionar varias señales personalizadas.
| Nombre | Descripción |
|---|---|
| WZ-74.mq5 | Asesor experto creado con el asistente, cuyo encabezado muestra los archivos utilizados en su creación. |
| SignalWZ-74.mqh | Archivo de clase de señal personalizada. |
| 74-5.onnx | Red ONNX para la señal patrón 5. |
| 74-1.onnx | Red ONNX para la señal patrón 1. |
| 74-0.onnx | Red ONNX para la señal patrón 0. |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18776
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.
Algoritmo del átomo artificial — Artificial Atom Algorithm (A3)
Características del Wizard MQL5 que debe conocer (Parte 73): Uso de patrones de Ichimoku y del ADX-Wilder
Particularidades del trabajo con números del tipo double en MQL4
Redes neuronales en el trading: Desentrañando las componentes estructuradas (Final)
- 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