Características del Wizard MQL5 que debe conocer (Parte 66): Uso de patrones FrAMA y Force Index con el núcleo de producto escalar
Introducción
En nuestro último artículo, en el que presentamos el conjunto de estos indicadores como fuente de patrones de señales de entrada para un asesor experto, los resultados de las pruebas prospectivas no fueron tan prometedores. Expusimos algunas razones que lo explicaban y también advertimos de que la formación y la optimización que llevamos a cabo tienen una vigencia de solo un año y que, por lo tanto, para cualquier patrón, es imprescindible realizar pruebas lo más exhaustivas posible con una gran cantidad de datos históricos. Como siempre, continuamos con ese artículo analizando aquellos patrones que lograron avanzar. Esto se hace mediante el aprendizaje automático.
Al aplicar algoritmos de aprendizaje automático en MQL5, OpenCL siempre es una opción; sin embargo, esto a menudo requiere disponer de hardware GPU. Es bueno tener esto, pero la biblioteca de código de Python se ha vuelto bastante extensa, y se pueden obtener muchas mejoras de eficiencia con solo una CPU. Eso es lo que exploramos en esta serie de artículos, y por eso, para este trabajo, como ya hemos hecho en algunos anteriores, programamos nuestras redes neuronales en Python porque la programación y el entrenamiento en Python son muy eficientes.
De los diez patrones que optimizamos o entrenamos en el artículo anterior, solo dos lograron superar las pruebas prospectivas. Patrón-6 y Patrón-9. Por lo tanto, continuamos probando estos resultados con una red neuronal, como hicimos en artículos anteriores, con la diferencia de que estamos utilizando una red neuronal convolucional, también conocida como CNN. Esta CNN implementará el núcleo del producto escalar. Sin embargo, como siempre ocurre con las implementaciones de Python, primero definimos las funciones indicadoras que necesitamos para proporcionar señales a nuestra red.
Función de media móvil adaptativa fractal (FrAMA)
El FrAMA es una media móvil dinámica que adapta su suavizado en función de las dimensiones fractales de los movimientos de precios. Esto tiende a hacer que responda mejor a los cambios importantes de precios y que sea menos sensible al ruido. Implementamos esta función en Python de la siguiente manera:
def FrAMA(df, period=14, price_col='close'): """ Calculate Fractal Adaptive Moving Average (FrAMA) for a DataFrame with price data. Args: df: Pandas DataFrame with a price column (default 'close'). period: Lookback period for fractal dimension (default 20). price_col: Name of the price column (default 'close'). Returns: Pandas DataFrame with a single column 'main' containing FrAMA values. """ prices = df[price_col] frama = pd.Series(index=prices.index, dtype=float) for t in range(period, len(prices)): # 1. High-Low range (volatility proxy) high = prices.iloc[t-period:t].max() low = prices.iloc[t-period:t].min() range_hl = high - low # 2. Fractal Dimension (simplified) fd = 1.0 + (np.log(range_hl + 1e-9) / np.log(period)) # Avoid log(0) # 3. Adaptive EMA smoothing factor alpha = 2.0 / (period * fd + 1) # 4. Update FrAMA (recursive EMA) frama.iloc[t] = alpha * prices.iloc[t] + (1 - alpha) * frama.iloc[t-1] return pd.DataFrame({'main': frama})
Nuestra función anterior nos brinda flexibilidad de entrada, ya que acepta un marco de datos de pandas con parámetros personalizables para la columna de precios y el período. Utiliza un indicador de volatilidad para el rango máximo-mínimo para estimar la volatilidad del mercado y calcula una dimensión fractal simplificada para medir la complejidad de los precios. Nuestra función actualiza el FrAMA mediante un EMA recursivo.
Si analizamos nuestro código, lo primero que hacemos es extraer la columna de precio especificada del marco de datos de entrada. Esta extracción garantiza que la función funcione con cualquier columna de precio especificada por el usuario. Evitamos la codificación rígida y mantenemos la compatibilidad. Al utilizar esto, puede ser una buena idea mejorar nuestra función verificando que la columna de precio de entrada exista en el marco de datos y asegurándonos de que los datos de precios estén limpios, sin NaN ni valores faltantes, dado que FrAMA es sensible a las entradas de precios.
A continuación, inicializamos una serie vacía de pandas con el mismo índice, ya que esto preasigna memoria para los valores de FrAMA, asegurando la alineación con el índice del marco de datos de entrada, para actualizaciones iterativas eficientes. Al utilizar esto, FrAMA requiere una ventana de cálculo retrospectivo para realizar el cálculo, lo que significa que los primeros valores son NaN y deben gestionarse o convertirse en ceros. También es importante garantizar la coherencia del índice de precios, por ejemplo, mediante la alineación precio-tiempo, para evitar desajustes.
Con esto, entramos en el bucle for, donde calculamos el rango máximo-mínimo durante el período de análisis retrospectivo para estimar la volatilidad. Para cada paso de tiempo t, tomamos los precios máximo y mínimo en la ventana de t períodos y calculamos su diferencia. El rango de valores máximos y mínimos sirve como indicador de la volatilidad del mercado, lo cual es importante para determinar la dimensión fractal. Un rango más amplio implica una mayor volatilidad, lo que influye en la naturaleza adaptativa de FrAMA. En la práctica, un mecanismo de manejo de errores que detecte valores NaN puede contribuir en cierta medida a solucionar el problema de los datos faltantes.
A continuación, calculamos la dimensión fractal simplificada utilizando la relación logarítmica del rango alto-bajo. El término equivalente épsilon 1e-9 evita la división por cero o el logaritmo de cero en el caso de que range-hl sea cero. La dimensión fractal, fd, mide la complejidad o la tendencia de los precios. Un fd más alto, es decir, más cercano a 2, significa que estamos en un mercado volátil y ruidoso. Por otro lado, un fd más bajo, más cercano a 1, sugiere mercados con tendencia. Este parámetro, por lo tanto, determina la adaptabilidad de FrAMA. El valor 1e-9 es un truco de estabilidad numérica que garantiza que sea lo suficientemente pequeño como para no distorsionar los resultados. Además, la dimensión fractal utilizada aquí es una versión simplificada; para formatos más complejos se pueden usar métodos de conteo de cajas. Después de esto, calculamos el factor de suavizado, alfa.
Este es un factor de suavizado para la media móvil exponencial basado en la dimensión fractal y el período. Es importante porque el alfa adaptativo determina la capacidad de respuesta de FrAMA a los nuevos precios. Un fd más alto, que se obtiene en un mercado más ruidoso, reduce el alfa, lo que ralentiza la EMA para filtrar el ruido. Un fd más bajo, que se obtiene en mercados con tendencia, aumenta el alfa, lo que hace que FrAMA sea más sensible a los cambios de precio.
Por lo tanto, esta fórmula equilibra la capacidad de respuesta y la suavidad. El periodo se puede ajustar para controlar la sensibilidad. Es importante asegurarse de que el denominador period * fd + 1 sea positivo para evitar problemas de división.
Nuestra siguiente línea de código actualiza el valor de FrAMA en el instante t utilizando la fórmula EMA; que es una combinación ponderada del precio actual y el valor FrAMA anterior. Esto es importante porque el cálculo recursivo de la EMA es la base de FrAMA y produce una media móvil adaptativa suavizada que se ajusta a las condiciones del mercado a través del alfa. Los cambios que se pueden realizar en esta implementación incluyen, en primer lugar, que los valores de FrAMA no sean NaN, y que estas comprobaciones también incluyan los casos límite del búfer FrAMA.
La última línea de código de la función FrAMA devuelve los valores de FrAMA como un marco de datos de pandas con una sola columna llamada main. Esto estandariza el formato de salida para garantizar la compatibilidad con otras herramientas de análisis técnico o bibliotecas de gráficos. El uso del nombre de columna 'main' es convencional para indicadores con un único búfer. Al integrarlo en diferentes entornos donde este nombre sea un punto de referencia, se puede personalizar a 'FrAMA' o algo más específico. También es necesario asegurarse de que el marco de datos devuelto coincida con el índice del marco de datos de entrada.
Función osciladora del índice de fuerza
Este oscilador mide la fuerza de los movimientos de precios al combinar los cambios en el precio y el volumen de negociación. Esto se suaviza con una EMA para enfatizar las tendencias o los cambios de tendencia. Implementamos esto en Python de la siguiente manera:
def ForceIndex(df, period=14, price_col='close', volume_col='tick_volume'): """ Calculate Force Index for a DataFrame with price and volume data. Args: df: Pandas DataFrame with columns for price and volume (default 'close' and 'volume'). period: Smoothing window for EMA (default 13). price_col: Name of the price column (default 'close'). volume_col: Name of the volume column (default 'volume'). Returns: Pandas DataFrame with a single column 'main' containing Force Index values. """ closes = df[price_col] volumes = df[volume_col] # 1. Raw Force Index = Price Delta * Volume price_delta = closes.diff() raw_force = price_delta * volumes # 2. Smooth with EMA alpha = 2.0 / (period + 1) force_index = pd.Series(index=closes.index, dtype=float) for t in range(1, len(raw_force)): if pd.isna(raw_force.iloc[t]): force_index.iloc[t] = np.nan else: force_index.iloc[t] = alpha * raw_force.iloc[t] + (1 - alpha) * force_index.iloc[t-1] return pd.DataFrame({'main': force_index})
Nuestra función descrita anteriormente, al igual que FrAMA, ofrece flexibilidad en la entrada de datos, ya que acepta columnas de precio y volumen personalizables. Utiliza un cálculo de fuerza sin suavizar que combina el cambio de precio y el volumen para medir la presión de compra y venta. Realiza un suavizado EMA para reducir el ruido y enfatizar los movimientos sostenidos. Gestiona los valores NaN. Finalmente, estandariza el resultado al devolver un marco de datos para facilitar su integración.
Si analizamos las líneas, lo primero que hacemos es extraer el precio de cierre y el volumen del marco de datos de entrada de la serie de pandas. Esto es importante porque aísla los datos relevantes para los cálculos, lo que permite flexibilidad en los nombres de las columnas y un procesamiento eficiente. Entre los posibles cambios se incluyen validar que price_col y volume_col existan en el marco de datos para evitar errores; y garantizar que los datos de volumen no sean negativos y que los datos de precio estén limpios, ya que el Force Index depende de ambos.
A continuación, definimos el delta de precio como la diferencia entre los precios de cierre. El método que utilizamos en Python devuelve un búfer que calcula la diferencia entre precios de cierre consecutivos para medir el movimiento de los precios. Esto es importante porque la variación del precio refleja la dirección y la magnitud de la deriva ponderada por volumen del mercado, que es un componente clave del Force Index. Los valores NaN del primer índice deben ser gestionados convirtiéndolos a cero, y también deben abordarse los casos de valores faltantes.
A continuación, determinamos el valor del índice de fuerza bruta, que es el producto del delta de precios y nuestros volúmenes. Python simplifica mucho la programación al permitir multiplicar arrays de forma vectorizada. Este producto representa la fuerza de la fluctuación de los precios. Combina el dinamismo de los precios con el volumen para cuantificar la presión de compra o venta. Una variación importante del precio con un volumen elevado suele indicar una mayor convicción en el mercado. Al realizar este producto vectorial, además del código que compartimos anteriormente, es una buena idea asegurarse de que los búferes de delta de volumen y precio estén alineados por índice para evitar errores de cálculo. Cabe destacar también que este índice de fuerza bruto puede presentar ruido, por lo que se puede aplicar un suavizado en una etapa posterior.
A continuación, establecemos el valor alfa. Este es el cálculo del factor de suavizado EMA basado en el período especificado utilizando la fórmula EMA estándar de 2/(N + 1). Esto es importante porque determina la ponderación de los datos nuevos frente a los datos históricos en el Force Index suavizado. Un alfa menor, que proviene de un período más largo, tiende a producir resultados más suaves. Utilizamos un período predeterminado de 14, y esto es lo habitual. Sin embargo, deberán realizarse ajustes en función del plazo que se utilice. Por lo general, los períodos más cortos funcionan bien para el trading intradía, mientras que los períodos más largos podrían ser útiles para marcos temporales diarios o más amplios. También es importante asegurarse de que el valor del período sea positivo para evitar errores de división por cero.
A continuación, inicializamos el búfer del índice de fuerza real a partir del marco de datos pandas de entrada. Esto simplemente llena un búfer vacío con el mismo índice que los datos de precios para almacenar valores suavizados del Force Index. Es importante porque sirve como una forma de preasignar memoria y también garantiza la alineación del índice, dos cosas que facilitan los cálculos iterativos de EMA. Se esperan valores iniciales de NaN, ya que el primer índice de fuerza requiere un valor previo.
Por lo tanto, comprobamos esto verificando si el índice de fuerza bruto en el tiempo t es NaN. Esto garantiza la solidez al gestionar explícitamente los datos faltantes, lo que evita cálculos de EMA no válidos. Los datos del mundo real a menudo contienen lagunas, por lo que, por trivial que parezca, no debe pasarse por alto. De hecho, registrar o marcar los valores NaN durante la depuración es una buena práctica.
A continuación, actualizamos el búfer del índice de fuerza en cada instante t, utilizando la fórmula EMA. Esto combina el índice de fuerza bruta actual con el valor suavizado anterior. Esto es importante, ya que se trata de un paso fundamental de suavizado que reduce el ruido en el índice de fuerza bruto y resalta las tendencias sostenidas o los cambios de tendencia. En este paso, es importante asegurarse de que force_index[t-1] se inicialice correctamente, ya que NaN suele ser el valor para los primeros índices. Por lo tanto, es necesario manejar adecuadamente los casos límite de este búfer.
Una vez hecho esto, devolvemos los valores suavizados del índice de fuerza como un marco de datos de pandas con una sola columna llamada main. Como ya se ha comentado anteriormente con FrAMA, el formato de salida estandarizado permite una mejor integración y visualización en formatos o herramientas secundarias, según las necesidades del usuario. La etiqueta 'main' tiende a coincidir con las convenciones del análisis técnico. Es importante asegurarse de que el índice del marco de datos coincida con el de la entrada para una fusión sin problemas.
Una vez definidas nuestras funciones indicadoras en Python, es momento de analizar los dos patrones de señal que pudieron avanzar en el artículo anterior. Estos eran el patrón 6 y el patrón 9. Estamos implementando esto como un vector simple de 2 bits que actúa como entrada para nuestra red neuronal. Estos "bits" representan el optimismo y el pesimismo, con valores de 0 o 1 en cada índice. En resumen, el vector de entrada tiene un tamaño de 2 y en el primer índice registramos 0 si no se cumplen TODAS las condiciones alcistas o 1 si se cumplen todas. De manera similar, en el segundo índice, registramos 0 si NO se cumplen TODAS las condiciones bajistas o 1 si se cumplen TODAS.
Anteriormente, hemos considerado escenarios en los que desglosamos cada uno de los componentes de la señal alcista y bajista para obtener vectores de entrada más elaborados para nuestras redes neuronales. Los avances que se derivaban de esto no eran tan prometedores, y parecían más similares al enfoque que probamos de combinar múltiples patrones en un único Asesor Experto, también en un artículo anterior.
Sin embargo, este análisis podría ser erróneo, por lo que los lectores pueden usar y modificar el código fuente adjunto y realizar pruebas independientes para llegar a sus propias conclusiones. El código adjunto está diseñado para ser utilizado en el asistente MQL5, y hay guías aquí sobre cómo hacerlo.
Función Característica 6
Nuestra implementación de la función Pattern-6 en Python genera una matriz bidimensional de señales binarias de 0 o 1, como se explicó anteriormente. Comenzamos inicializando nuestro array de salida de características con ceros. Este resultado es una matriz NumPy 2D, el equivalente a una matriz de 2 columnas. El número de filas de esta 'matriz' se establece según el tamaño o la longitud de uno de los marcos de datos de entrada 'one_df'. Esta inicialización configura la estructura de salida para almacenar las señales alcistas y bajistas por separado. La inicialización a cero significa que no se generan señales a menos que se cumplan las condiciones del patrón a, lo que proporciona un "borrón y cuenta nueva".
Al implementar esto, es importante asegurarse de que la longitud del primer data frame de pandas de entrada coincida con la del segundo 'two_df', así como con la del data frame de precios 'price_df', para evitar la desalineación de índices. La estructura 2D es clave para distinguir las señales bajistas de las alcistas, por lo que es importante comprobar que la forma coincida con las expectativas. Así es como lo implementamos:
def feature_6(one_df, two_df, price_df): """ Generate binary signals based on sustained price-FrAMA alignment and Force Index momentum. Args: one_df: DataFrame with FrAMA values ('main' column). two_df: DataFrame with Force Index values ('main' column). price_df: DataFrame with price data ('close' column). Returns: 2D NumPy array with bullish (column 0) and bearish (column 1) signals. """ feature = np.zeros((len(one_df), 2)) feature[:, 0] = ((price_df['close'] > one_df['main']) & (price_df['close'].shift(1) > one_df['main'].shift(1)) & (price_df['close'].shift(2) > one_df['main'].shift(2)) & (two_df['main'] > 0.0) & (two_df['main'].shift(1) < two_df['main'].shift(2))).astype(int) feature[:, 1] = ((price_df['close'] < one_df['main']) & (price_df['close'].shift(1) < one_df['main'].shift(1)) & (price_df['close'].shift(2) < one_df['main'].shift(2)) & (two_df['main'] < 0.0) & (two_df['main'].shift(1) < two_df['main'].shift(2))).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
A continuación, procedemos a definir nuestras expectativas alcistas o el primer valor del índice en cada fila. Recapitulando del artículo anterior, si el precio actual está por encima de FrAMA; y el precio anterior también está por encima de FrAMA anterior; y hace dos períodos se dio esta misma situación; y el índice de fuerza actual es positivo; además, hace un período el índice de fuerza estaba por debajo de donde está ahora, al igual que hace dos períodos; entonces tenemos una configuración alcista.
Esta primera línea de la columna resume el escenario alcista, asegurando que la señal solo se active cuando la tendencia del precio se mantenga consistentemente por encima del FrAMA adaptativo, lo que indica una tendencia alcista. El índice de fuerza sirve para confirmar la fuerte presión compradora. La comprobación de tres periodos añade robustez, mientras que la comprobación del índice de fuerza omite los movimientos débiles. Durante la implementación, es importante verificar que todos los marcos de datos de entrada estén alineados por índice y contengan datos válidos. Las operaciones shift() crean valores NaN para las primeras filas, que deben ser procesados antes de que se puedan devolver los valores de los indicadores.
La segunda columna define la señal bajista con las siguientes condiciones análogas: el precio actual está por debajo de FrAMA; el precio anterior también está por debajo de FrAMA; la misma condición se dio hace dos períodos; el índice de fuerza actual es negativo; y el índice de fuerza de hace un período está por encima del índice de fuerza actual y del índice de fuerza de hace dos períodos. La forma de ‘n’ en el índice de fuerza indica un aumento del impulso bajista.
Este patrón refleja el patrón alcista de oportunidades de venta. Identifica tendencias bajistas sostenidas con una fuerte presión vendedora. La simetría garantiza una lógica coherente en ambos patrones. Al igual que en el patrón alcista, es vital garantizar la coherencia de los datos. También deberían realizarse pruebas para determinar si este patrón se presenta con suficiente frecuencia. La comprobación de impulso del índice de fuerza (`shift(1) < shift(2)`) puede actuar de forma diferente en mercados bajistas que en mercados alcistas. Por lo tanto, es importante analizar el desempeño histórico para verificar su fiabilidad.
Lo siguiente que hacemos es establecer las dos primeras filas de la matriz de características a cero, ya que las operaciones '.shift(1)' y '.shift(2)' producen NaN para estas filas, lo que hace que estas condiciones no estén definidas. Esto evita señales no válidas en las filas iniciales donde los datos históricos son insuficientes y garantiza una salida limpia y utilizable sin procesamiento posterior manual. Esta comprobación es fundamental para la robustez y, dado que este patrón puede implementarse teniendo más de dos desplazamientos (como cuando los períodos comparados superan los 2), entonces es importante verificar que establecer solo las dos primeras filas es suficiente en función del número máximo de desplazamientos utilizado. Si se añaden desplazamientos adicionales, esto deberá ajustarse en consecuencia.
En resumen, nuestra característica 6 se centra en un cruce sostenido de precio - FrAMA de al menos 3 períodos con un índice de fuerza positivo y reciente en forma de U. Esto confirma si las tendencias alcistas tienen continuidad. El patrón bajista es inverso a este, ya que se basa en un cruce bajista sostenido de precio - FrAMA durante al menos 3 períodos más un índice de fuerza negativo con un patrón reciente en forma de n. También confirma que las tendencias bajistas se mantienen intactas.
Una diferencia clave entre ambos, además de ser simétricos y opuestos, es que la comprobación del sentimiento del índice de fuerza requiere exclusivamente un aumento reciente de magnitud, que puede comportarse de manera diferente en mercados alcistas frente a bajistas debido a la asimetría. Por ejemplo, los mercados tienden a caer más rápido de lo que suben.
Función Característica-9
Esta implementación en Python de feature_9 se centra en la alineación del sentimiento en un solo período a través del precio, FrAMA y el índice de fuerza, lo que los hace más sensibles a los cambios a corto plazo que 'feature_6'. Comenzamos como lo hicimos con la función 9, inicializando nuestra salida objetivo con ceros. Esta configuración es similar a la de la característica 6, donde el tamaño inicial del array NumPy 2D de salida se establece para que coincida con la longitud de uno de los data frames de entrada, one-df. La implementamos en Python de la siguiente manera:
def feature_9(one_df, two_df, price_df): """ Generate binary signals based on single-period momentum alignment of price, FrAMA, and Force Index. Args: one_df: DataFrame with FrAMA values ('main' column). two_df: DataFrame with Force Index values ('main' column). price_df: DataFrame with price data ('close' column). Returns: 2D NumPy array with bullish (column 0) and bearish (column 1) signals. """ feature = np.zeros((len(one_df), 2)) feature[:, 0] = ((price_df['close'] > price_df['close'].shift(1)) & (one_df['main'] > one_df['main'].shift(1)) & (two_df['main'] > two_df['main'].shift(1))).astype(int) feature[:, 1] = ((price_df['close'] < price_df['close'].shift(1)) & (one_df['main'] < one_df['main'].shift(1)) & (two_df['main'] < two_df['main'].shift(1))).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
Para ambas funciones de características, one-df se refiere al marco de datos FrAMA, mientras que two-df se refiere al marco de datos del índice de fuerza. Por lo tanto, como mencionamos en la característica 6, necesitamos verificar que un marco de datos, dos marcos de datos y el marco de datos de precios tengan la misma longitud para evitar discrepancias en la forma. Esta matriz/arreglo 2D separa los patrones alcistas y bajistas, por lo que es importante verificar el tamaño de sus filas.
La condición alcista es también lo que comprobamos inicialmente con este patrón, a continuación. Sus condiciones son: que el precio actual sea superior al cierre anterior; que el FrAMA actual sea superior al FrAMA anterior, y que el índice de fuerza actual esté por encima del último valor. Esto refleja el impulso alcista a corto plazo, donde el precio, el FrAMA y el índice de fuerza están subiendo, lo que indica una presión de compra coordinada. También es un poco más sensible que la función 6 porque solo comprueba un único período. Esto podría hacerlo más adecuado para el scalping o el trading a corto plazo.
Durante su uso, es importante garantizar la alineación de los datos en todas las entradas para evitar comparaciones desalineadas. Dado que este patrón es sensible a cambios de un solo período, necesitamos probar la frecuencia de su señal para evitar operar en exceso en caso de que los mercados se encuentren con datos ruidosos. Visualizar las señales en un gráfico de precios para asegurar que se alineen con el volumen y el sentimiento esperados también puede ser constructivo.
Establecimos el patrón bajista para la característica 9 como el cumplimiento de las condiciones: el precio actual es inferior al precio anterior; el FrAMA actual es inferior al FrAMA anterior; y el índice de fuerza actual es inferior al índice de fuerza anterior. Esto refleja un sentimiento bajista a corto plazo en el que las tres métricas están disminuyendo, lo que indica una presión de venta coordinada. Su enfoque en un solo período tiende a hacerla reactiva ante las caídas inmediatas.
Una vez más, tal como vimos con el patrón alcista, la validación de la integridad de los datos y la prueba de la frecuencia de la señal son vitales. Las señales bajistas pueden activarse con mayor frecuencia en mercados volátiles, por lo que un análisis del rendimiento histórico es importante al ajustar esta estrategia.
A continuación, establecemos las primeras filas del array de salida a cero debido al uso de la función shift, como ya se mencionó anteriormente, y luego devolvemos el array como salida de la función.
Red neuronal convolucional con núcleo de producto escalar
El algoritmo de aprendizaje automático que hemos elegido para extender los patrones de FrAMA y el índice de fuerza es una red neuronal convolucional que utiliza el núcleo de producto escalar. Logramos esto mediante la clase DotProductConv1D. Esta clase es un módulo de red neuronal de PyTorch que combina convoluciones 1D con un mecanismo de atención de producto escalar, inspirado en la arquitectura Transformer.
Procesa datos de entrada con forma [lote, canales, ancho] y genera un único valor por muestra en el rango [0, 1]. Un valor cercano a 0 sería una previsión bajista, mientras que un valor cercano a 1 sería alcista. El mecanismo de atención basado en el producto escalar permite que la red se centre en las partes relevantes de la secuencia de entrada, mientras que las capas convolucionales proyectan los datos en un espacio adecuado para el cálculo de la atención.
El uso de este núcleo dentro de una CNN es beneficioso por varias razones. En primer lugar, permite centrarse selectivamente en las características relevantes. El mecanismo de atención del producto escalar calcula puntuaciones de similitud entre los vectores de consulta y clave, lo que permite a la red ponderar con mayor peso los pasos o características clave importantes. Esto puede resultar beneficioso en algunas series temporales, por ejemplo, cuando ciertos períodos son más informativos, como los períodos volátiles frente a los no volátiles. En segundo lugar, introduce una conciencia del contexto global.
A diferencia de las CNN tradicionales, que se basan en núcleos de tamaño fijo y campos receptivos locales, la atención del producto escalar permite que cada paso de tiempo preste atención a todos los demás pasos de tiempo, capturando así dependencias de largo alcance sin aumentar el tamaño del núcleo. En tercer lugar, la ponderación dinámica, donde las puntuaciones de atención se calculan dinámicamente en función de la entrada, hace que el modelo se adapte a patrones variables en los datos, como diferentes condiciones de mercado dentro de la serie temporal.
Por último, existen ventajas complementarias derivadas de la combinación con redes neuronales convolucionales (CNN), que son buenas para la extracción de características locales, y con el núcleo de producto escalar, que es bueno para las relaciones globales. El mecanismo de atención basado en el producto escalar también es computacionalmente eficiente en secuencias de longitud moderada, mientras que la CNN 1D reduce la dimensionalidad de las entradas, lo que hace que todo el modelo sea ligero en comparación con los modelos de transformadores completos. Nuestra lista para esta clase de red es la siguiente:
class DotProductConv1D(nn.Module): def __init__(self, in_channels=1, out_channels=1, kernel_size=3): super().__init__() self.kernel_size = kernel_size self.padding = kernel_size // 2 # Projections for dot product attention (1D convolution) self.query = nn.Conv1d(in_channels, out_channels, kernel_size=1) self.key = nn.Conv1d(in_channels, out_channels, kernel_size=1) self.value = nn.Conv1d(in_channels, out_channels, kernel_size=1) # Output projection to produce a single value per sample self.proj = nn.Sequential( nn.Conv1d(out_channels, 1, kernel_size=1), nn.AdaptiveAvgPool1d(1), # Reduce width to 1 (global average pooling) nn.Sigmoid() # Ensures output in [0, 1] ) def forward(self, x): B, C, W = x.shape # [Batch, Channels, Width] # Compute Q/K/V (all [B, out_channels, W]) q = self.query(x) k = self.key(x) v = self.value(x) # Dot product attention attn = torch.bmm(q.transpose(1, 2), k) # [B, W, W] attn = F.softmax(attn / (W ** 0.5), dim=-1) # Scaled softmax # Apply attention to values out = torch.bmm(v, attn.transpose(1, 2)) # [B, out_channels, W] # Project to [B, 1, 1] and squeeze to [B, 1] out = self.proj(out) # [B, 1, 1] return out.squeeze(-1) # [B, 1]
Lo primero que hacemos arriba es definir la clase como una subclase de 'nn.Module' de PyTorch. Esto permite utilizarlo como un módulo de red neuronal con gestión automática de parámetros y compatibilidad con GPU. A continuación, inicializamos el módulo con parámetros para los canales de entrada, los canales de salida para las proyecciones de atención y un tamaño de núcleo para los cálculos de relleno. Estos parámetros definen la capacidad de la red y su compatibilidad con los datos de entrada. A continuación, calculamos el tamaño del relleno para mantener la longitud de la secuencia de entrada después de la convolución, utilizando la división entera para garantizar un relleno simétrico.
A continuación, definimos tres capas convolucionales 1D que utilizaremos para proyectar la entrada en tensores de consulta, clave y valor para el mecanismo de atención. Cada una tiene un núcleo de tamaño 1 y actúa como una transformación lineal por paso de tiempo. A continuación, definimos el proceso de proyección de salida. Reduce el canal de salida de atención a 1. Aplica un método de agrupación de promedio global para reducir la dimensión temporal a un único valor. Finalmente, transforma la salida en un único escalar en el rango [0, 1].
Una vez definido este "encabezado de clase", procedemos a describir la función de avance. Lo primero que hacemos dentro del forward es aplicar las capas convolucionales de consulta, clave y valor al tensor de entrada 'x' que tiene forma '[B, C, W]' produciendo tres tensores de forma [B, out_channels, W]. Una vez hecho esto, calculamos las puntuaciones de atención del producto escalar realizando una multiplicación de matrices por lotes ('bmm') entre el tensor de consulta transpuesto (`[B, W, out_channels]`) y el tensor clave (`[B, out_channels, W]`), lo que produce una matriz de atención con forma [B, W, W].
A continuación, aplicamos una función soft max escalada a las puntuaciones de atención dividiendo la longitud de la secuencia por la raíz cuadrada para estabilizar los gradientes. Finalmente, normalizamos a lo largo de la última dimensión para producir pesos de atención que sumen 1. La salida de la función forward es lo que definimos a continuación, realizando una multiplicación de matrices por lotes entre 'v' ('[B, out_channels, W]') y la matriz de atención transpuesta ('[B, W, W]') para producir una forma de salida de '[B, out_channels, W]'.
Luego pasamos la salida de atención a través de la tubería de proyección ('self.proj') para producir un tensor con la forma '[B, 1, 1]'. A continuación, simplemente extraemos la última dimensión de manera que la salida resultante tenga la forma '[B, 1]'.
Pruebas de funcionamiento
A continuación se presentan los resultados de las pruebas realizadas con los dos patrones de características 6 y 9. Ambos parecen superar las pruebas prospectivas, pero como siempre, nuestro entrenamiento se basó en un conjunto de datos muy limitado y, por lo tanto, se requiere una mayor diligencia y pruebas exhaustivas por parte del lector antes de poder extraer conclusiones a largo plazo de los informes de las pruebas:
Para el patrón 6.
Para el patrón 9.
Conclusión
Hemos demostrado cómo el aprendizaje automático, mediante el uso de un núcleo de producto escalar, puede utilizarse para ampliar y posiblemente aprovechar el potencial preliminar indicado por las señales del indicador FrAMA y el oscilador del índice de fuerza. Solo probamos los patrones 6 y 9, lo cual es muy limitante; sin embargo, además de los otros 8 que no consideramos, existen varias otras implementaciones del emparejamiento de solo estos dos indicadores que se pueden explorar.
| Nombre | Descripción |
|---|---|
| wz_66.mq5 | El asistente ha creado un asesor experto cuyo encabezado indica los archivos incluidos. |
| SignalWZ_66.mqh | Archivo de clase de señal personalizada. |
| 66_6.0nnx | Archivo ONNX Patrón 6. |
| 66_9.onnx | Archivo ONNX Patrón 9. |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18188
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
Redes neuronales en el trading: Pipeline de pronóstico inteligente (Time-MoE)
Particularidades del trabajo con números del tipo double en MQL4
Aprendizaje automático y Data Science (Parte 41): Detección de patrones en los mercados de divisas y de valores mediante YOLOv8
- 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



