English Русский 中文 Deutsch 日本語
preview
Características del Wizard MQL5 que debe conocer (Parte 70): Uso de patrones del SAR y el RVI con una red de kernel exponencial

Características del Wizard MQL5 que debe conocer (Parte 70): Uso de patrones del SAR y el RVI con una red de kernel exponencial

MetaTrader 5Integración |
37 0
Stephen Njuki
Stephen Njuki

Introducción

En el artículo anterior, presentamos este par complementario formado por el indicador SAR parabólico (SAR) y el oscilador del índice de vigor relativo (RVI). De nuestras pruebas con 10 patrones, tres no lograron realizar walk-forward limpios; concretamente, los indexados 1, 2 y 6. Nuestra indexación de estos patrones del 0 al 9 nos permite calcular fácilmente el valor de mapa de bits que permite su uso exclusivo por parte del Asesor Experto. Por ejemplo, si un patrón tiene el índice 1, entonces tenemos que establecer el parámetro 'PatternsUsed' en 2 elevado a la potencia 1, lo que da como resultado 2. 

Si el índice es 2, entonces esto es 2 elevado a la potencia 2, que da como resultado 4, y así sucesivamente. El valor máximo que se le puede asignar a este parámetro, de forma significativa, es 1023, ya que solo tenemos 10 parámetros. Cualquier número entre 0 y 1023 que no sea una potencia exacta de 2 representaría una combinación de estos patrones, y el lector podría explorar la posibilidad de configurar el Asesor experto para que utilice múltiples patrones. Sin embargo, basándonos en nuestros argumentos y en los resultados de las pruebas presentadas en artículos anteriores, optamos por no explorar esta posibilidad dentro de esta serie, por el momento. 

Tal como prometimos en uno de los artículos recientes, ahora vamos a intentar reactivar las tres señales de los patrones 1, 2 y 6 que no lograron mostrar walk-forward limpios en el artículo anterior, mediante aprendizaje supervisado. Al aplicar el aprendizaje automático a estas señales indicadoras MQL5, recurrimos a Python para que nos ayude con la codificación y el entrenamiento de un modelo de red. Esto se debe a la eficiencia que es capaz de proporcionar, incluso sin una GPU. Al usar Python, dependemos del módulo de Python de MetaTrader, que nos permite conectarnos al servidor de un bróker de MetaTrader una vez que proporcionamos un nombre de usuario y una contraseña. 

Una vez establecida la conexión a través del módulo Python de MetaTrader 5, tenemos acceso a los datos de precios del bróker. Python también cuenta con bibliotecas para indicadores técnicos, pero requieren instalación y a menudo se presentan en formatos algo esotéricos. Por suerte, implementar nuestros propios indicadores desde cero es un proceso relativamente sencillo. Por lo tanto, comenzamos implementando nuestros dos indicadores, el SAR y el RVI, en Python.


Función SAR parabólica (SAR)

El SAR es un indicador de seguimiento de tendencias que se utiliza para detectar posibles puntos fractales de reversión en la dirección del precio. Utiliza la colocación de puntos para marcar una tendencia predominante, situándose los puntos en el lado de los posibles puntos de stop-loss. Así, una tendencia alcista tendría sus puntos por debajo de los mínimos, mientras que una tendencia bajista los tendría por encima de los máximos. Codificamos la función para calcular el SAR de la siguiente manera:

def SAR(df: pd.DataFrame, af: float = 0.02, af_max: float = 0.2) -> pd.DataFrame:
    """
    Calculate Parabolic SAR indicator and append it as 'SAR' column to the input DataFrame.
    
    Args:
        df (pd.DataFrame): DataFrame with 'high', 'low', 'close' columns
        af (float): Acceleration factor, default 0.02
        af_max (float): Maximum acceleration factor, default 0.2
        
    Returns:
        pd.DataFrame: Input DataFrame with new 'SAR' column
    """
    if not all(col in df.columns for col in ['high', 'low', 'close']):
        raise ValueError("DataFrame must contain 'high', 'low', 'close' columns")
    if af <= 0 or af_max <= 0 or af > af_max:
        raise ValueError("Invalid acceleration factors")
    if df.empty:
        raise ValueError("DataFrame is empty")

    result_df = df.copy()
    result_df['SAR'] = 0.0
    
    sar = df['close'].iloc[0]
    ep = df['high'].iloc[0]
    af_current = af
    trend = 1 if len(df) > 1 and df['close'].iloc[1] > df['close'].iloc[0] else -1
    
    for i in range(1, len(df)):
        prev_sar = sar
        high, low = df['high'].iloc[i], df['low'].iloc[i]
        
        if trend > 0:
            sar = prev_sar + af_current * (ep - prev_sar)
            if low < sar:
                trend = -1
                sar = ep
                ep = low
                af_current = af
            else:
                if high > ep:
                    ep = high
                    af_current = min(af_current + af, af_max)
        else:
            sar = prev_sar + af_current * (ep - prev_sar)
            if high > sar:
                trend = 1
                sar = ep
                ep = high
                af_current = af
            else:
                if low < ep:
                    ep = low
                    af_current = min(af_current + af, af_max)
        
        result_df.loc[i, 'SAR'] = sar
    
    return result_df

La función que hemos implementado anteriormente toma un marco de datos de pandas con columnas de precio máximo, mínimo y de cierre, así como dos parámetros flotantes para el factor de aceleración. El resultado final añade los valores calculados como una columna al marco de datos de entrada, en una nueva columna denominada 'SAR'. Su lógica general está diseñada para rastrear la dirección de la tendencia y actualizar el SAR, basándose en el punto extremo y el factor de aceleración.

Si analizamos los detalles del código, comenzamos creando una copia del marco de datos de entrada para evitar modificar el original. Esto extrae las columnas de datos altas y bajas como matrices NumPy para un cálculo eficiente. A continuación, inicializamos el array 'sar' con ceros para almacenar los valores de 'sar'. Esto es importante porque garantiza la integridad de los datos y prepara la estructura para los cálculos SAR. El uso de `.copy()` evita efectos secundarios no deseados. Los arrays de NumPy también tienden a ofrecer un mejor rendimiento en comparación con las series de pandas al realizar cálculos iterativos.

Después de esto, inicializamos la tendencia como alcista o bajista según la comparación entre los dos primeros cierres. El punto extremo también se asigna al máximo inicial, y al SAR inicial también se le asigna el precio mínimo inicial. El factor de aceleración comienza con el valor de entrada 'af-start'. Esto es vital porque define un punto de partida para los cálculos SAR, al asumir una tendencia alcista inicial. La elección del valor low[0] para el SAR inicial se alinea con el papel del SAR como stop-loss para una tendencia alcista, ya que está por debajo del precio mínimo por el peso del parámetro 'af-start' que ajusta su resta del mínimo en línea con la volatilidad del mercado.

A continuación, procedemos a actualizar la fórmula SAR. Las actualizaciones proporcionadas corresponden al período actual, acercándolo al punto extremo según la escala del factor de aceleración. Esto es importante ya que es fundamental para la fórmula SAR. Una compensación parabólica del precio a medida que se desarrolla una tendencia. Es importante asegurarse, al implementar esto, de que 'af' sea pequeño para no tener un cambio demasiado brusco una vez que se observe una tendencia. También es fundamental vigilar la estabilidad numérica en mercados volátiles.

Una vez hecho esto, procedemos a establecer la lógica de inversión de la tendencia. Cuando se produce una tendencia alcista y el SAR supera el mínimo actual, se produce un cambio de tendencia a la baja. El SAR se reinicia al punto extremo anterior, y el nuevo punto extremo se establece en el valor mínimo actual. El factor de aceleración también se reajusta a su valor predeterminado. 

Esto es importante porque la detección de cambios de tendencia es una característica clave del SAR para señalar los puntos de entrada y salida. Esta condición garantiza que el SAR se mantenga por debajo del precio en una tendencia alcista. La prueba de sensibilidad a la inversión se puede realizar ajustando el parámetro 'af-increment'.

Si nos encontramos en una situación alcista continua, entonces debemos seguir actualizando nuestros valores de buffer SAR y, lo que es más importante, incrementar el 'af-increment'. El SAR se mantiene limitado al mínimo entre el valor actual y los dos últimos mínimos para evitar que invada el rango de precios. Además, una vez que el valor máximo supera el punto extremo, este último se actualiza, mientras que el factor de aceleración también recibe un incremento, siempre que no supere su límite máximo establecido por 'af-max'. Este paso de mantenimiento es importante porque garantiza que el SAR siga siendo un nivel de stop-loss válido y que solo se acelere en consonancia con la tendencia. El uso de dos mínimos anteriores aporta cierta solidez, sin embargo, puede requerir algún ajuste al enfrentarse a plazos más cortos.

A continuación, se establece la lógica bajista del SAR. En muchos sentidos, esto refleja la lógica alcista que acabamos de mencionar. En resumen, si el SAR cae por debajo del máximo actual, la tendencia se revierte y pasa a ser alcista. Si no se produce ningún cambio de tendencia, la SAR se mantiene limitada al máximo entre la SAR actual y los máximos anteriores. Tanto el punto extremo como el factor de aceleración se actualizan. Este paso completa la capacidad del SAR para rastrear tanto las tendencias alcistas como las bajistas de forma simétrica. Siempre es importante garantizar la simetría en la lógica de las tendencias alcistas y bajistas para evitar cualquier sesgo. Se puede realizar una validación con datos históricos para confirmar la exactitud de la reversión.

Una vez resuelta la lógica SAR, procedemos a la asignación de salida. Básicamente, esto proporciona los valores SAR calculados a una nueva columna 'SAR' en el marco de datos y luego la devuelve. Esto es fundamental porque integra el indicador en el marco de datos para permitir un análisis o visualización más detallados. Al utilizarlo, puede ser una buena idea verificar que la columna SAR coincida con los datos de precios. Esto se puede lograr utilizando bibliotecas de gráficos como Matplotlib para visualizar los puntos SAR.

En resumen, el SAR es más adecuado para mercados con tendencia, donde puede ayudar a señalar los niveles de stop-loss e identificar los puntos de reversión. No funcionaría bien en mercados laterales o volátiles. Aunque el ajuste de parámetros no es tan crítico para el SAR, puede ser importante para algunos activos, ya que el inicio y el incremento del factor de aceleración pueden necesitar ajustes respecto a sus valores habituales de 0,02 y 0,2. La validación mediante pruebas retrospectivas con datos históricos del bróker puede resultar muy útil. Las principales limitaciones del SAR son su tendencia a presentar retrasos en mercados de rápido movimiento, lo que puede generar muchas señales falsas.


Función del Relative Vigor Index (RVI)

El objetivo principal del RVI es realizar un seguimiento de la fuerza de una tendencia, algo que consideramos sinónimo de impulso. Esto se consigue comparando la posición del precio de cierre con respecto al rango de negociación y suavizando esta relación con medias móviles. Se trata de un oscilador que puede servir para confirmar la tendencia comprobando el impulso o detectando divergencias. Lo implementamos en Python de la siguiente manera:

def RVI(df: pd.DataFrame, period: int, signal_period: int) -> pd.DataFrame:
    """
    Calculate Relative Vigor Index (RVI) with signal line and append as 'RVI' and 'RVI_Signal' columns.
    
    Args:
        df (pd.DataFrame): DataFrame with 'open', 'high', 'low', 'close' columns
        period (int): Lookback period for RVI calculation
        signal_period (int): Lookback period for signal line calculation
        
    Returns:
        pd.DataFrame: Input DataFrame with new 'RVI' and 'RVI_Signal' columns
    """
    # Input validation
    if not all(col in df.columns for col in ['open', 'high', 'low', 'close']):
        raise ValueError("DataFrame must contain 'open', 'high', 'low', 'close' columns")
    if period < 1 or signal_period < 1:
        raise ValueError("Period and signal period must be positive")
        
    # Create a copy to avoid modifying the input DataFrame
    result_df = df.copy()
    
    # Calculate price change and range
    close_open = df['close'] - df['open']
    high_low = df['high'] - df['low']
    
    # Calculate SMA for numerator and denominator
    num = close_open.rolling(window=period).mean()
    denom = high_low.rolling(window=period).mean()
    
    # Calculate RVI
    result_df['RVI'] = num / denom * 100
    
    # Calculate signal line (SMA of RVI)
    result_df['RVI_Signal'] = result_df['RVI'].rolling(window=signal_period).mean()
    
    return result_df

En general, en nuestro código anterior estamos tomando un marco de datos de pandas con las columnas 'open', 'high', 'low' y 'close'. Además, las entradas incluyen un "período" para la media móvil simple (SMA) del búfer de señal. La salida agrega el RVI, que ha sido suavizado junto con una señal RVI, a las columnas del marco de datos. Básicamente, la lógica calcula el RVI a partir del promedio móvil de (close-open) y (high-low). A continuación, esto se suaviza con una SMA de 4 periodos, y generamos una línea de señal con un periodo de SMA definido por el usuario.

Si ahora analizamos esto línea por línea, lo primero que hacemos en nuestra función es crear una copia del marco de datos de entrada. Esto sirve como una forma de preservar los datos originales. Es importante porque nos permite evitar modificaciones no deseadas en el marco de datos de entrada. El uso de la función de copia nos ayuda fácilmente a lograr esto y a evitar "efectos secundarios" no deseados.

A continuación calculamos el RVI. Este cálculo del RVI bruto lo establece como una relación entre la variación del precio y el rango de negociación. Es importante porque refleja la intensidad del movimiento de precios en relación con el rango del día, y esto constituye la base del RVI. Es importante asegurarse de que el valor "alto" y el "bajo" no sean iguales para evitar divisiones por cero. Este problema, sin embargo, se aborda en el siguiente paso. Por ahora, la fórmula asume un impulso intradiario.

Para manejar los errores de división por cero, reemplazamos los valores infinitos, provenientes de una posible división por cero anterior cuando el valor alto es igual al valor bajo, con NaN. Cada Nan se reemplaza entonces por cero. Esto es importante para garantizar la estabilidad numérica y también para evitar errores en los cálculos posteriores. A modo de orientación, esta es una solución simplista, ya que el lector puede considerar enfoques de manejo alternativos en caso de que los valores cero distorsionen excesivamente el búfer de salida RVI.

Con esto, procedemos a suavizar el RVI y también a definir la línea de señal. El suavizado aplica una SMA de 4 períodos al RVI sin procesar para reducir la susceptibilidad al ruido e incluso a los valores cero impares que puedan encontrarse, como se mencionó anteriormente. Esto ayuda a crear la línea indicadora RVI. Una vez configurado esto, también aplicamos un período SMA definido por el usuario para suavizar aún más este RVI ya suavizado con el fin de generar la línea de señal. Este paso es fundamental, ya que el suavizado hace que el RVI sea más interpretable. La línea de señal también nos ayuda a detectar de forma más definitiva los cruces de la implementación de esto a menudo utiliza el SMA fijo de 4 períodos para suavizar el RVI sin procesar, sin embargo, siempre hay margen para ajustar el período de la señal SMA si se requiere un ajuste fino. Se pueden considerar periodos comprendidos entre 6 y 14.

Una vez hecho esto, se asigna el resultado. Tanto el RVI suavizado como el RVI de la línea de señal reciben columnas de datos asignadas en el marco de datos de salida. En teoría, esto permite que tanto el RVI como su búfer de señal participen en análisis o visualizaciones adicionales. La representación gráfica de ambos búferes puede ayudar a resaltar los puntos de cruce, por ejemplo. 

En resumen, el RVI se aplica para confirmar la fuerza de la tendencia, sirviendo como un indicador de impulso. También puede detectar divergencias en una fase temprana, como cuando el precio alcanza nuevos máximos mientras que el RVI no lo hace. Además, los cruces con su línea de señal ayudan a confirmar las perspectivas alcistas o bajistas. El ajuste de parámetros para el RVI se centra principalmente en ajustar el "período" del SMA utilizado para suavizar el RVI de la línea de señal. Los periodos más cortos, en la región de 6, pueden utilizarse para señales más rápidas/frecuentes, mientras que los periodos de alrededor de 14 sirven para señales más suaves. La validación mediante pruebas de cruces y divergencias de RVI en datos históricos es importante para establecer la fiabilidad de la señal. La principal limitación del RVI es su tendencia a presentar retrasos debido al doble suavizado que utiliza. También puede tener dificultades para generar señales significativas en mercados de baja volatilidad. 


Complementariedad SAR RVI

Esta combinación de indicadores, al igual que la mayoría de las que hemos considerado para esta serie, se selecciona porque cada indicador complementa al otro. El SAR es un indicador de seguimiento de tendencias que se representa en un gráfico de precios. Sin embargo, el RVI es un oscilador que se representa en una ventana separada del gráfico de precios y se utiliza principalmente para medir el impulso. El SAR está mejor posicionado para establecer límites de pérdidas, mientras que el RVI es más adecuado para confirmar tendencias o identificar divergencias.

Ambas están implementadas en Python, utilizando pandas para el manejo de datos. La función de NumPy es acelerar los cálculos. La lógica del SAR, que es iterativa, tiende a ser más compleja debido a los cambios de tendencia. Por otro lado, las operaciones vectorizadas de RVI son más sencillas, aunque son susceptibles a escenarios de división por cero. Por lo tanto, el rendimiento de este dúo de indicadores se ve mejorado por el uso de matrices NumPy para SAR, dado el uso de cálculos iterativos, y por los marcos de datos de pandas al manejar el RVI vectorizado.

Otras medidas que el lector puede optar por implementar para este par incluyen pruebas retrospectivas en Python con bibliotecas como zipline; visualización, especialmente para operadores manuales, de los gráficos de puntos SAR, así como del RVI con su línea de señal; manejo de errores complementando el código anterior con validación de entrada, como verificar que las columnas de datos requeridas estén presentes en el marco de datos pandas o que el período del indicador sea válido; y finalmente, se pueden agregar extensiones para cada indicador. Por ejemplo, el RVI se complementa con la detección de divergencias, o podría emplear métodos de suavizado alternativos.


Patrones de señal seleccionados

Característica-1

La mayoría de los patrones del último artículo pudieron proporcionar algún tipo de prueba walk-forward, sin embargo, los patrones indexados 1, 2 y 6 no lo hicieron. En el pasado, nos hemos centrado en patrones que avanzaban explorando si el aprendizaje automático podía agudizar aún más su filo. Sin embargo, para este artículo, y probablemente para muchos otros similares que le seguirán, utilizaremos el aprendizaje automático para intentar mejorar el desempeño de los patrones que no logran avanzar tras las pruebas preliminares.

En Python, al igual que en MQL5, a cada uno de estos patrones hay que asignarle una función que genere una señal de verdadero o falso. Verdadero si la señal de patrón está presente, y falso en caso contrario. Estos resultados, verdaderos o falsos, se presentan como señales binarias de 0 y 1, ya que combinan estrategias de negociación de dos indicadores y datos de precios. Cada función crea una matriz de características con dos columnas. La primera columna, la columna 0, registra señales alcistas. La segunda columna, la columna-1, registra las señales bajistas. En cada columna, se registra un 0 si no hay señal y un 1 si hay señal.

Como ya se explicó en el artículo anterior, la función 1 detecta tendencias cuando el precio se mantiene por encima o por debajo del SAR y, al mismo tiempo, el RVI está subiendo o bajando. Se centra en una simbiosis sostenida entre el SAR y el precio, así como en el impulso del RVI. Implementamos esta función en Python de la siguiente manera:

def feature_1(sar_df, rvi_df, price_df):
    """
    
    """
    feature = np.zeros((len(sar_df), 2))
    
    feature[:, 0] = ((price_df['low'].shift(1) > sar_df['SAR'].shift(1)) &
                     (price_df['low'] > sar_df['SAR']) &
                     (rvi_df['RVI'] > rvi_df['RVI'].shift(1))).astype(int)
    
    feature[:, 1] = ((price_df['high'].shift(1) < sar_df['SAR'].shift(1)) &
                     (price_df['high'] < sar_df['SAR']) &
                     (rvi_df['RVI'] < rvi_df['RVI'].shift(1))).astype(int)
    
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Lo primero que hacemos en nuestro código anterior es inicializar un array relleno de ceros con la forma [longitud de los data frames de entrada, 2]. Las dos "columnas" asignadas al final están destinadas a capturar señales alcistas y bajistas, respectivamente. Este paso es importante, ya que garantiza una estructura inicial limpia para almacenar nuestras señales binarias y evita tener valores sin inicializar. El uso de np.zeros se debe a razones de eficiencia y sirve para prevenir resultados inesperados en las operaciones numéricas, ya que los valores NaN también se pueden usar automáticamente para rellenar una matriz vacía.

Una vez realizada la inicialización, establecemos el valor para la señal alcista. Para la columna 0, asignamos un 1 si el mínimo anterior está por encima del SAR actual en una tendencia alcista sostenida; y el mínimo actual también está por encima del SAR; con el RVI actual por encima de la lectura anterior indicando un impulso alcista. Es importante utilizar TODAS estas señales para aprovechar una fuerte tendencia alcista, como lo confirman tanto el SAR para la tendencia como el RVI para el impulso. Es importante asegurarse, al hacer esto, de que `shift(1)` alinee los datos correctamente. La falta de datos puede provocar errores graves. El uso de 'astype(int)' ayuda a convertir valores booleanos a binario, 0 y 1.

Una vez definida la columna alcista, procedemos a asignar la columna bajista. Los requisitos bajistas reflejan los alcistas, por lo que es improbable obtener un vector de características con valores 1 en las columnas alcista y bajista. Además, el tamaño de nuestro vector de características está restringido a 2 para poder capturar únicamente el optimismo "completo" o el pesimismo "completo". Hemos hablado de vectores mucho más largos, e incluso los hemos implementado, para que puedan gestionar las señales de los indicadores individuales en cada índice. Las pruebas realizadas bajo estas condiciones no arrojaron buenos resultados, con una ventana de prueba muy limitada, y en base a esto nos mantenemos fieles a la opción de dos tamaños: alcista y bajista.

Así pues, para la segunda columna, establecemos la señal bajista cuando el máximo anterior está por debajo del SAR anterior y el máximo actual también está por debajo del SAR actual. Esto se produce cuando el RVI actual es inferior al RVI anterior, lo que indica una disminución del impulso. Todos estos patrones se unifican para definir una fuerte tendencia bajista gracias a la complementariedad del SAR y el RVI. De forma similar a la lógica alcista anterior, necesitamos comprobar la alineación de los datos y también manejar los valores NaN de 'shift'.

Una vez definidas las señales alcistas y bajistas, procedemos a realizar ajustes en nuestra matriz de salida teniendo en cuenta las comparaciones de desplazamiento. Esto lo hacemos asignando ceros a las dos primeras filas para evitar señales no válidas. También es importante para evitar señales espurias derivadas de datos incompletos al principio. Por regla general, siempre es recomendable anular las filas iniciales al utilizar datos rezagados para mantener la robustez. Los resultados de las pruebas, que abarcaron el entrenamiento y la validación walk-forward, nos proporcionan el siguiente informe:

r1

c1

Parece que estamos obteniendo mejores resultados con la introducción del aprendizaje supervisado. A continuación se muestra el gráfico que obtuvimos para el patrón 1 sin aprendizaje supervisado:

c1_antiguo

Característica-2

El tercer patrón que probamos tampoco logró avanzar. Este patrón característico, como se analizó en el artículo anterior, detecta posibles reversiones donde el precio cruza el SAR y el RVI muestra cierto impulso. También incorpora la dirección del movimiento de precios, utilizando el precio de cierre, para obtener una dirección adicional. Implementamos esto en Python de la siguiente manera:

def feature_2(sar_df, rvi_df, price_df):
    """

    """
    feature = np.zeros((len(sar_df), 2))
    
    feature[:, 0] = ((price_df['high'].shift(1) <= sar_df['SAR'].shift(1)) &
                     (price_df['low'] >= sar_df['SAR']) &
                     (rvi_df['RVI'] > rvi_df['RVI'].shift(1)) &
                     (price_df['close'].shift(1) > price_df['close'])).astype(int)
    
    feature[:, 1] = ((price_df['low'].shift(1) >= sar_df['SAR'].shift(1)) &
                     (price_df['high'] <= sar_df['SAR']) &
                     (rvi_df['RVI'].shift(1) > rvi_df['RVI']) &
                     (price_df['close'] > price_df['close'].shift(1))).astype(int)
    
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

También comenzamos aquí inicializando una matriz rellena de ceros que está diseñada para recibir señales tanto alcistas como bajistas. Como se mencionó anteriormente, esto se debe a que garantiza que no tengamos valores NaN al inicio y que podamos obtener una salida predecible de la función. Una inicialización coherente es vital para el procesamiento posterior. 

A continuación, procedemos a definir las condiciones para una señal alcista. Para que registremos un 1, deben cumplirse todas estas condiciones; de lo contrario, se asignará un cero. Por eso, trabajar con matrices rellenas de ceros es una forma segura de evitar entradas falsas. Entonces, si el máximo anterior está en o por debajo del SAR anterior; y el mínimo actual está en o por encima del SAR actual significa que hemos tenido un cambio; con el RVI en aumento; y finalmente el precio de cierre disminuye para confirmar el contexto de divergencia. Esto ayuda a capturar reversiones alcistas donde el precio supera el SAR con impulso RVI. Este patrón está sujeto a varios requisitos, y todos deben confirmarse antes de asignar el valor verdadero o 1. 

Para las condiciones bajistas, las establecemos en el segundo índice del array NumPy de características. Para que exista una tendencia bajista, el mínimo anterior está en o por encima del SAR anterior; el máximo actual está en o por debajo del SAR actual; el RVI está cayendo; y el precio de cierre está subiendo como confirmación de la configuración de divergencia. El patrón de señal 2 detecta divergencias bajistas, y es importante garantizar la coherencia de los datos entre los diferentes marcos de datos. A continuación, anulamos las filas iniciales de nuestra matriz de características asignándoles ceros para tener en cuenta el uso de comparaciones de desplazamiento. Esto evita que las señales no válidas se deban a la falta de datos retrasados. Es una práctica habitual para las características de series temporales que presentan desfases.

Esta característica-2, en teoría, podría usarse junto con el patrón de señal-1 anterior porque existe una relación de complementariedad. La función 1 es un sistema puramente de seguimiento de tendencias, mientras que la función 2 apunta a las reversiones en los puntos de divergencia, lo que puede incorporar el swing trading para un sistema más robusto. Sin embargo, como se argumentó y demostró en artículos anteriores, el emparejamiento de diferentes patrones inevitablemente conduce a la cancelación cruzada prematura de señales y, por lo tanto, las pruebas de dicho sistema deben realizarse con una gran cantidad de datos históricos y los avances también deben ser definitivos. Tras entrenar y probar este patrón, obtenemos el informe que aparece a continuación, que abarca tanto el período de entrenamiento como el de prueba. Una vez más, parece que estamos obteniendo resultados más favorables con el uso del aprendizaje automático:

r2

c2

A continuación se muestra el gráfico que obtuvimos para el patrón 2 sin el aprendizaje supervisado:

c2_antiguo

Característica-6

Nuestra última prueba, de las 10 originales del artículo anterior, se basa en el patrón de señal 6. Esto utiliza el cruce RVI con su línea de señal en lugar de solo el impulso RVI para la confirmación. Se centra en señales de tendencia fuertes que presentan eventos de cruce entre SAR y RVI. Implementamos esto en Python de la siguiente manera:

def feature_6(sar_df, rvi_df, price_df):
    """

    """
    feature = np.zeros((len(sar_df), 2))
    
    feature[:, 0] = ((price_df['low'].shift(1) > sar_df['SAR'].shift(1)) &
                     (price_df['low'] > sar_df['SAR']) &
                     (rvi_df['RVI'] > rvi_df['RVI_Signal']) &
                     (rvi_df['RVI'].shift(1) < rvi_df['RVI_Signal'].shift(1))).astype(int)
    
    feature[:, 1] = ((price_df['high'].shift(1) < sar_df['SAR'].shift(1)) &
                     (price_df['high'] < sar_df['SAR']) &
                     (rvi_df['RVI'] < rvi_df['RVI_Signal']) &
                     (rvi_df['RVI'].shift(1) > rvi_df['RVI_Signal'].shift(1))).astype(int)
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Como siempre, comenzamos dimensionando una matriz NumPy rellena de ceros para almacenar los resultados previstos. Esta inicialización es importante y coherente con las demás funciones para garantizar una salida limpia y libre de NaN. También ayuda a mantener una estructura de matriz predecible para su uso posterior.

Una vez hecho esto, definimos las condiciones para la señal alcista, que se registran en el primer índice, cero. Registramos un 1 si el mínimo anterior está por encima del SAR; y el mínimo actual está por encima del SAR actual; y el RVI actual también está por encima de su línea de señal; y finalmente el RVI anterior estaba por debajo de su señal.

Una vez definido el patrón alcista, también establecemos el bajista, que se produce cuando el máximo anterior está por debajo del SAR anterior; y esto se mantiene porque el máximo actual también está por debajo del SAR actual; y el RVI actual está por debajo de su línea de señal; habiendo el RVI previo habiendo estado por encima de su línea de señal. El entrenamiento y la prueba de este patrón, al igual que con los otros dos, nos proporcionan el siguiente informe de prueba. Una vez más, parece que obtenemos un comportamiento walk-forward favorable.

r6

c6

A continuación se muestra el gráfico que obtuvimos para el patrón 6 sin aprendizaje supervisado:

c6_antiguo


La Red

En las pruebas, con los 3 patrones seleccionados anteriormente, hemos utilizado una red neuronal que es una red neuronal convolucional 1D. Tiene tres capas convolucionales, cada una de las cuales aumenta exponencialmente el número de filtros y el tamaño del núcleo. A continuación se aplica el max-pooling, que es una operación de aplanamiento. Finalmente, empleamos las capas totalmente conectadas para llegar al resultado final. Esto lo codificamos de la siguiente manera en Python:

class ExpConv1DNetwork(nn.Module):
    def __init__(self, input_length, input_channels=1, base_filters=16, base_kernel_size=3, exp_base=2):
        super(ExpConv1DNetwork, self).__init__()
        self.conv_layers = nn.ModuleList()
        self.pool_layers = nn.ModuleList()
        
        for i in range(3):
            filters = int(base_filters * (exp_base ** i))
            kernel_size = int(base_kernel_size * (exp_base ** i)) | 1
            self.conv_layers.append(
                nn.Conv1d(
                    in_channels=input_channels if i == 0 else int(base_filters * (exp_base ** (i-1))),
                    out_channels=filters,
                    kernel_size=kernel_size,
                    padding='same'
                )
            )
            # Use smaller kernel size for pooling to prevent size reduction to 0
            self.pool_layers.append(nn.MaxPool1d(kernel_size=2, ceil_mode=True))
        
        self.flatten_size = self._get_flatten_size(input_length, input_channels, base_filters, exp_base)
        self.flatten = nn.Flatten()
        self.dense1 = nn.Linear(self.flatten_size, 128)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.5)
        self.dense2 = nn.Linear(128, 1)
    
    def _get_flatten_size(self, input_length, input_channels, base_filters, exp_base):
        current_length = input_length
        current_channels = input_channels
        
        for i in range(3):
            current_channels = int(base_filters * (exp_base ** i))
            # Update length after pooling
            current_length = (current_length + 1) // 2  # Ceiling division for ceil_mode=True
        
        return current_channels * current_length
    
    def forward(self, x):
        for conv, pool in zip(self.conv_layers, self.pool_layers):
            x = self.relu(conv(x))
            x = pool(x)
        x = self.flatten(x)
        x = self.relu(self.dense1(x))
        x = self.dropout(x)
        x = self.dense2(x)
        return x

Esta red espera una entrada 1D con forma de tamaño de lote, canales de entrada, longitud de entrada. Además, se puede personalizar mediante parámetros como base-filters, base-kernel-size y exp-base. Nuestro resultado es una única salida escalar entrenada para estar en el rango de 0 a 1, donde 0 pronostica una tendencia bajista y 1 proyecta una tendencia alcista. Sin duda, habría sido necesario un análisis detallado de las líneas de código de esta clase, ya que esta clase es clave para establecer el rendimiento de nuestros patrones de señal renovados. Sin embargo, este tema queda aparcado por ahora, dada la extensión de nuestro artículo, pero lo tendremos en cuenta en artículos similares en el futuro.


Conclusión

Hemos explorado los beneficios potenciales de agregar aprendizaje supervisado a patrones de señales que no pudieron realizar validaciones walk-forward cuando se trataba de una ventana de datos limitada del par GBP/CHF de solo 2 años. Según nuestras pruebas, realizadas de nuevo en condiciones muy restringidas, parece haber una diferencia positiva con respecto a lo que obtuvimos en el artículo anterior. Esto es alentador. Sin embargo, como siempre, se requiere una investigación independiente y más exhaustiva por parte del lector antes de que pueda adoptar cualquiera de las ideas aquí presentadas. 


Nombre Descripción
WZ-70.mq5 Archivo cuyo encabezado indica los archivos utilizados en el ensamblado del asistente.
SignalWZ_70.mqh Archivo de clase de señal personalizada, utilizado por el asistente MQL5.
70_1.onnx Modelo de red exportado para el patrón-1.
70_2.onnx Modelo de red exportado para el patrón-2.
70_6.onnx Modelo de red exportado para el patrón-6.

Para los lectores nuevos, este código adjunto está diseñado para ser ensamblado en un Asesor Experto mediante el asistente MQL5. Aquí hay tutoriales sobre cómo hacerlo.

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

Archivos adjuntos |
WZ-70.mq5 (6.92 KB)
SignalWZ_70.mqh (14.34 KB)
70_1.onnx (153.86 KB)
70_2.onnx (153.86 KB)
70_6.onnx (153.86 KB)
Simulación de mercado: Position View (XIII) Simulación de mercado: Position View (XIII)
En este artículo mostraré cómo implementar, sin mucho esfuerzo, una indicación que muestre si una posición genera pérdidas o beneficios. El procedimiento es sencillo y eficaz. Incluso sin grandes conocimientos, este indicador te permitirá reconocer con facilidad cuándo conviene cerrar una posición. Así evitarás resultados inesperados, ya que el cálculo refleja el resultado efectivo que produciría el cierre de la posición.
Algoritmo de Aprendizaje Competitivo — Competitive Learning Algorithm (CLA) Algoritmo de Aprendizaje Competitivo — Competitive Learning Algorithm (CLA)
El artículo presenta el Algoritmo de Aprendizaje Competitivo (CLA), un nuevo método de optimización metaheurística basado en la modelización del proceso educativo. El algoritmo organiza la población de soluciones en clases con estudiantes y profesores, donde los agentes aprenden a través de tres mecanismos: siguiendo a los mejores de la clase, usando la experiencia personal y compartiendo conocimientos entre clases.
Del básico al intermedio: Clases (I) Del básico al intermedio: Clases (I)
En este artículo explicamos qué es una clase y por qué surgió este concepto. Aunque el tema es interesante, aquí nos centraremos en los principios que rigen la programación en MQL5. Este artículo es solo una introducción.
De novato a experto: Revelando las sombras de las velas japonesas (mechas) De novato a experto: Revelando las sombras de las velas japonesas (mechas)
En este análisis, damos un paso adelante para descubrir la acción del precio subyacente que se esconde tras las mechas de las velas japonesas. Al integrar una función de visualización de mechas en el Market Periods Synchronizer, mejoramos la herramienta con mayor profundidad analítica e interactividad. Este sistema mejorado permite a los traders visualizar los rechazos de precios en marcos temporales superiores directamente en gráficos de marcos temporales inferiores, revelando estructuras detalladas que antes estaban ocultas.