English Русский 中文 Deutsch 日本語 Português
preview
Uso conjunto de PSAR, Heiken Ashi y Deep Learning para el trading

Uso conjunto de PSAR, Heiken Ashi y Deep Learning para el trading

MetaTrader 5Ejemplos |
157 0
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera

Introducción

Encontrar una ventaja puede ser la diferencia entre el éxito y el fracaso. Con tantos datos disponibles, los comerciantes recurren cada vez más a tecnologías avanzadas como el aprendizaje profundo para obtener una ventaja. Este viaje implica combinar herramientas tradicionales de análisis técnico con modelos modernos de inteligencia artificial (IA) para evaluar y adaptarse rápidamente a las condiciones del mercado. Para explorar este potencial, hemos desarrollado un script en Python diseñado para combinar el poder predictivo del aprendizaje profundo con indicadores comerciales probados y verdaderos. Este script no pretende ser un sistema de trading completo; más bien, es una herramienta para la experimentación rápida: una forma de probar rápidamente si una estrategia de trading tiene el potencial de ser rentable. Paralelamente, un script de MetaTrader 5 utiliza principios similares para llevar este enfoque a un entorno comercial en vivo, cerrando la brecha entre las pruebas retrospectivas teóricas y la aplicación en el mundo real.


Creación del modelo ONNX de aprendizaje profundo

En el acelerado mundo del trading de divisas, mantenerse a la vanguardia es crucial. Por eso hemos desarrollado un script en Python que aprovecha el poder del aprendizaje profundo para predecir los movimientos de los pares de divisas. ¡Hagamos un recorrido amistoso por este emocionante fragmento de código y veamos cómo hace su magia!

Nuestro script comienza importando todas las herramientas necesarias: piense en ello como si reuniera ingredientes para una receta compleja. Utilizamos bibliotecas como TensorFlow y Keras, que son los motores detrás de muchas aplicaciones de IA modernas. También utilizamos MetaTrader 5 para obtener datos reales de Forex.

import MetaTrader5 as mt5
import tensorflow as tf
import numpy as np
import pandas as pd
import tf2onnx
import keras

A continuación, configuramos algunos parámetros importantes. Estamos analizando 120 días de datos históricos para el par EURUSD. ¿Por qué 120? Es un punto óptimo que nos brinda suficiente historial para detectar patrones sin abrumar nuestro modelo.

Luego utilizamos MetaTrader 5 para obtener estos datos. Imagínese enviar a un viajero en el tiempo para recopilar información de precios de los últimos 120 días. Una vez que tenemos estos datos, los organizamos cuidadosamente en un formato que nuestra IA pueda entender.

inp_history_size = 120

sample_size = inp_history_size*3*20
symbol = "EURUSD"
optional = "D1_2024"
inp_model_name = str(symbol)+"_"+str(optional)+".onnx" 

if not mt5.initialize():
    print("initialize() failed, error code =",mt5.last_error())
    quit()

# we will save generated onnx-file near the our script to use as resource
from sys import argv
data_path=argv[0]
last_index=data_path.rfind("\\")+1
data_path=data_path[0:last_index]
print("data path to save onnx model",data_path)

# and save to MQL5\Files folder to use as file
terminal_info=mt5.terminal_info()
file_path=terminal_info.data_path+"\\MQL5\\Files\\"
print("file path to save onnx model",file_path)

# set start and end dates for history data
from datetime import timedelta, datetime
#end_date = datetime.now()
end_date = datetime(2024, 1, 1, 0)
start_date = end_date - timedelta(days=inp_history_size*20*3)

# print start and end dates
print("data start date =",start_date)
print("data end date =",end_date)

# get rates
eurusd_rates = mt5.copy_rates_from(symbol, mt5.TIMEFRAME_D1, end_date, sample_size)

Los datos financieros sin procesar pueden ser confusos, por eso los limpiamos. Nos centramos en los precios de cierre, es decir, el precio final de cada día de negociación. También escalamos estos datos para que estén entre 0 y 1. Este paso es crucial porque ayuda a nuestro modelo de IA a aprender de forma más efectiva, como si tradujera todo a un lenguaje común.

from sklearn.preprocessing import MinMaxScaler
scaler=MinMaxScaler(feature_range=(0,1))
scaled_data = scaler.fit_transform(data)

No queremos que nuestro modelo memorice los datos, por lo que lo dividimos en dos partes: datos de entrenamiento (80%) y datos de prueba (20%). Es como darle a nuestro estudiante de IA un libro de texto para estudiar (datos de entrenamiento) y luego una prueba separada para evaluar sus conocimientos (datos de prueba).

# scale data
from sklearn.preprocessing import MinMaxScaler
scaler=MinMaxScaler(feature_range=(0,1))
scaled_data = scaler.fit_transform(data)

# training size is 80% of the data
training_size = int(len(scaled_data)*0.80) 
print("Training_size:",training_size)
train_data_initial = scaled_data[0:training_size,:]
test_data_initial = scaled_data[training_size:,:1]

# split a univariate sequence into samples
def split_sequence(sequence, n_steps):
    X, y = list(), list()
    for i in range(len(sequence)):
       # find the end of this pattern
       end_ix = i + n_steps
       # check if we are beyond the sequence
       if end_ix > len(sequence)-1:
          break
       # gather input and output parts of the pattern
       seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
       X.append(seq_x)
       y.append(seq_y)
    return np.array(X), np.array(y)

# split into samples
time_step = inp_history_size
x_train, y_train = split_sequence(train_data_initial, time_step)
x_test, y_test = split_sequence(test_data_initial, time_step)

# reshape input to be [samples, time steps, features] which is required for LSTM
x_train =x_train.reshape(x_train.shape[0],x_train.shape[1],1)
x_test = x_test.reshape(x_test.shape[0],x_test.shape[1],1)

¡Ahora viene la parte emocionante: construir nuestro modelo de IA! Estamos utilizando una combinación de diferentes técnicas de IA:

  1.  Redes neuronales convolucionales (CNN): son excelentes para detectar patrones en secuencias, de la misma manera que nuestros ojos detectan patrones en imágenes.
  2.  Redes de memoria a corto y largo plazo (LSTM): son excelentes para comprender las dependencias a largo plazo en los datos y son perfectas para detectar tendencias a lo largo del tiempo.
  3.  Capas densas: ayudan a nuestro modelo a realizar sus predicciones finales.

También agregamos algunas capas de abandono para evitar que nuestro modelo se vuelva demasiado confiado y "sobreajuste" los datos.

# define model
from keras.models import Sequential
from keras.layers import Dense, Activation, Conv1D, MaxPooling1D, Dropout, Flatten, LSTM
from keras.metrics import RootMeanSquaredError as rmse
from tensorflow.keras import callbacks
model = Sequential()
model.add(Conv1D(filters=256, kernel_size=2, strides=1, padding='same', activation='relu', input_shape=(inp_history_size,1)))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(100, return_sequences = True))
model.add(Dropout(0.3))
model.add(LSTM(100, return_sequences = False))
model.add(Dropout(0.3))
model.add(Dense(units=1, activation = 'sigmoid'))
model.compile(optimizer='adam', loss= 'mse' , metrics = [rmse()])

# Set up early stopping
early_stopping = callbacks.EarlyStopping(
    monitor='val_loss',
    patience=20,
    restore_best_weights=True,
)

# model training for 300 epochs
history = model.fit(x_train, y_train, epochs = 300 , validation_data = (x_test,y_test), batch_size=32, callbacks=[early_stopping], verbose=2)

Entrenar el modelo es como enviar nuestra IA a la escuela. Le mostramos nuestros datos de entrenamiento muchas veces (hasta 300 épocas) y gradualmente aprende a predecir precios futuros basándose en patrones pasados. Usamos algo llamado "detención temprana" para asegurarnos de que nuestra IA no estudie durante demasiado tiempo y comience a memorizar en lugar de aprender.

Después del entrenamiento, probamos el rendimiento de nuestro modelo. Utilizamos métricas como RMSE (error cuadrático medio) para ver qué tan cerca están nuestras predicciones de los precios reales. Es como calificar los resultados de las pruebas de nuestra IA.

# evaluate training data
train_loss, train_rmse = model.evaluate(x_train,y_train, batch_size = 32)
print(f"train_loss={train_loss:.3f}")
print(f"train_rmse={train_rmse:.3f}")

# evaluate testing data
test_loss, test_rmse = model.evaluate(x_test,y_test, batch_size = 32)
print(f"test_loss={test_loss:.3f}")
print(f"test_rmse={test_rmse:.3f}")

El paso final es convertir nuestro modelo entrenado al formato ONNX. ONNX es como un lenguaje universal para modelos de IA: permite que nuestro modelo se utilice en diferentes entornos, incluidos nuestros scripts de MetaTrader.

# Define a function that represents your model
@tf.function(input_signature=[tf.TensorSpec([None, inp_history_size, 1], tf.float32)])
def model_function(x):
    return model(x)

output_path = data_path+inp_model_name
# Convert the model to ONNX
onnx_model, _ = tf2onnx.convert.from_function(
    model_function, 
    input_signature=[tf.TensorSpec([None, inp_history_size, 1], tf.float32)],
    opset=13,
    output_path=output_path
)

print(f"Saved ONNX model to {output_path}")


# save model to ONNX
output_path = data_path+inp_model_name
onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path)
print(f"saved model to {output_path}")

output_path = file_path+inp_model_name
onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path)
print(f"saved model to {output_path}")

No nos detenemos sólo en números: creamos varios gráficos para visualizar el rendimiento de nuestro modelo. Estos gráficos muestran cosas como el progreso de aprendizaje del modelo y cómo sus predicciones se comparan con los precios reales.

Al final de este script, tendremos un poderoso modelo ONNX listo para ser utilizado en nuestras estrategias de trading de divisas. Está entrenado con datos históricos de EURUSD y puede ayudar a predecir movimientos de precios futuros.

Recuerde que, si bien este modelo es sofisticado, no es una bola de cristal. El mercado Forex está influenciado por muchos factores complejos. Utilice siempre las predicciones de IA como una herramienta entre muchas en su conjunto de herramientas comerciales y nunca olvide la importancia de la gestión de riesgos.


Script de Python para probar una estrategia

Este script de Python es esencialmente una forma rápida de probar si una estrategia comercial tiene el potencial de ser rentable. No está pensado para ser un sistema comercial completo, sino más bien una herramienta para una experimentación rápida. Al combinar un modelo de aprendizaje profundo con análisis técnico, se busca ver si existe una ventaja viable en el mercado sin empantanarse de inmediato en optimizaciones complejas.

El script comienza conectándose a MetaTrader 5 para extraer datos de precios históricos del EURUSD. Utiliza datos horarios para capturar movimientos a corto plazo y aplica velas Heikin Ashi para suavizar el ruido en el mercado. Esto ayuda a identificar tendencias con mayor claridad, lo que es crucial cuando se prueba rápidamente una estrategia. Luego agrega algunos indicadores técnicos conocidos como PSAR, SMA, RSI y ATR. Estos indicadores sirven como una verificación rápida de las condiciones del mercado, brindando al modelo contexto para tomar decisiones de compra y venta.

import MetaTrader5 as mt5
import pandas as pd
import numpy as np
import onnxruntime as ort
from sklearn.preprocessing import MinMaxScaler
from ta.trend import PSARIndicator, SMAIndicator
from ta.momentum import RSIIndicator
from ta.volatility import AverageTrueRange
import matplotlib.pyplot as plt

# Inicializar conexión con MetaTrader5
if not mt5.initialize():
    print("Inicialización fallida")
    mt5.shutdown()

def get_historical_data(symbol, timeframe, start_date, end_date):
    rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
    df = pd.DataFrame(rates)
    df['time'] = pd.to_datetime(df['time'], unit='s')
    df.set_index('time', inplace=True)
    return df

def calculate_heikin_ashi(df):
    ha_close = (df['open'] + df['high'] + df['low'] + df['close']) / 4
    ha_open = (df['open'].shift(1) + df['close'].shift(1)) / 2
    ha_high = df[['high', 'open', 'close']].max(axis=1)
    ha_low = df[['low', 'open', 'close']].min(axis=1)
    
    df['ha_close'] = ha_close
    df['ha_open'] = ha_open
    df['ha_high'] = ha_high
    df['ha_low'] = ha_low
    return df

def add_indicators(df):
    # Calcular Heikin Ashi
    df = calculate_heikin_ashi(df)
    
    # PSAR con parámetros ajustados
    psar = PSARIndicator(df['high'], df['low'], df['close'], step=0.02, max_step=0.2)
    df['psar'] = psar.psar()
    
    # Añadir SMA
    sma = SMAIndicator(df['close'], window=50)
    df['sma'] = sma.sma_indicator()
    
    # Añadir RSI
    rsi = RSIIndicator(df['close'], window=14)
    df['rsi'] = rsi.rsi()
    
    # Añadir ATR para medir volatilidad
    atr = AverageTrueRange(df['high'], df['low'], df['close'], window=14)
    df['atr'] = atr.average_true_range()
    
    # Añadir filtro de tendencia simple
    df['trend'] = np.where(df['close'] > df['sma'], 1, -1)
    
    return df

El corazón del script radica en el uso de un modelo de aprendizaje profundo ONNX previamente entrenado. Al escalar los datos y alimentar este modelo con ellos, el script genera predicciones sobre hacia dónde podría dirigirse el mercado a continuación. Combina estas predicciones con los indicadores técnicos para crear un conjunto básico de reglas comerciales. Por ejemplo, busca oportunidades para entrar en largo cuando se dan ciertas condiciones, como que el precio esté por encima de la media móvil y el RSI muestre que el mercado no está sobrecomprado. Por el contrario, señala una posición corta cuando se cumplen las condiciones opuestas. El script incluso agrega mecanismos adaptativos de stop-loss y take-profit para gestionar el riesgo, aunque estos se mantienen relativamente simples para mantener el enfoque del script en las pruebas rápidas.

def prepare_data(df, window_size=120):
    scaler = MinMaxScaler(feature_range=(0, 1))
    scaled_data = scaler.fit_transform(df[['close']])
    
    X = []
    for i in range(window_size, len(scaled_data)):
        X.append(scaled_data[i-window_size:i])
    
    return np.array(X), scaler

def load_onnx_model(model_path):
    return ort.InferenceSession(model_path)

def predict_with_onnx(model, input_data):
    input_name = model.get_inputs()[0].name
    output_name = model.get_outputs()[0].name
    return model.run([output_name], {input_name: input_data})[0]

def backtest(df, model, scaler, window_size=120, initial_balance=10000):
    scaled_data = scaler.transform(df[['close']])
    
    predictions = []
    for i in range(window_size, len(scaled_data)):
        X = scaled_data[i-window_size:i].reshape(1, window_size, 1)
        pred = predict_with_onnx(model, X.astype(np.float32))
        predictions.append(scaler.inverse_transform(pred.reshape(-1, 1))[0, 0])
    
    df['predictions'] = [np.nan]*window_size + predictions
    
    # Nueva lógica de trading
    df['position'] = 0
    long_condition = (
        (df['close'] > df['predictions']) & 
        (df['close'] > df['psar']) & 
        (df['close'] > df['sma']) & 
        (df['rsi'] < 60) &  # Condición RSI menos estricta
        (df['ha_close'] > df['ha_open']) &
        (df['ha_close'].shift(1) > df['ha_open'].shift(1)) &
        (df['trend'] == 1)  # Solo comprar en tendencia alcista
    )
    short_condition = (
        (df['close'] < df['predictions']) & 
        (df['close'] < df['psar']) & 
        (df['close'] < df['sma']) & 
        (df['rsi'] > 40) &  # Condición RSI menos estricta
        (df['ha_close'] < df['ha_open']) &
        (df['ha_close'].shift(1) < df['ha_open'].shift(1)) &
        (df['trend'] == -1)  # Solo vender en tendencia bajista
    )
    
    df.loc[long_condition, 'position'] = 1  # Compra
    df.loc[short_condition, 'position'] = -1  # Venta
    
    # Implementar stop-loss y take-profit adaptativos
    sl_atr_multiple = 2
    tp_atr_multiple = 3
    
    for i in range(window_size, len(df)):
        if df['position'].iloc[i-1] != 0:
            entry_price = df['close'].iloc[i-1]
            current_atr = df['atr'].iloc[i-1]
            if df['position'].iloc[i-1] == 1:  # Posición larga
                sl_price = entry_price - sl_atr_multiple * current_atr
                tp_price = entry_price + tp_atr_multiple * current_atr
                if df['low'].iloc[i] < sl_price or df['high'].iloc[i] > tp_price:
                    df.loc[df.index[i], 'position'] = 0
            else:  # Posición corta
                sl_price = entry_price + sl_atr_multiple * current_atr
                tp_price = entry_price - tp_atr_multiple * current_atr
                if df['high'].iloc[i] > sl_price or df['low'].iloc[i] < tp_price:
                    df.loc[df.index[i], 'position'] = 0
    
    df['returns'] = df['close'].pct_change()
    df['strategy_returns'] = df['position'].shift(1) * df['returns']
    
    # Calcular balance
    df['cumulative_returns'] = (1 + df['strategy_returns']).cumprod()
    df['balance'] = initial_balance * df['cumulative_returns']
    
    return df

En cuanto a los resultados, el script ofrece una instantánea rápida del rendimiento de la estrategia. En este caso, obtuvo una rentabilidad total del 1,35%, lo que significa que convirtió 10.000 dólares en 10.135,02 dólares durante el periodo de prueba. Si bien no se trata de una ganancia espectacular, es un resultado positivo que sugiere que la estrategia tiene cierto mérito. El índice de Sharpe, una medida común de rendimiento ajustado al riesgo, fue de 0,39. Esta cifra indica que, si bien hubo ganancias, éstas conllevaron un riesgo considerable. Para una prueba rápida, esta relación es una forma útil de evaluar si vale la pena seguir con la estrategia. 

Retorno total: 1.35%
Ratio de Sharpe: 0.39
Balance final: $10135.02

Precio gráfico y estrategia

Heiken Ashi

Balance

Las visualizaciones generadas por el script proporcionan una descripción general útil de cómo funcionó la estrategia. El primer gráfico muestra la interacción entre el precio del mercado, las predicciones del modelo y las señales de compra y venta. El gráfico Heikin Ashi ofrece una visión más clara de las tendencias del mercado, ayudándole a ver si la estrategia está capturando los momentos adecuados para entrar y salir de las operaciones. Por último, la curva de capital ofrece una visión directa de cómo ha cambiado el saldo de la cuenta a lo largo del tiempo, destacando tanto los períodos de crecimiento como las caídas.

En resumen, este script está diseñado para una experimentación rápida. Le ayuda a evaluar rápidamente si una estrategia tiene potencial sin tener que profundizar demasiado en ajustes. Si bien demuestra que la estrategia puede generar algunas ganancias, el modesto índice de Sharpe sugiere que aún queda trabajo por hacer. Esto lo convierte en un punto de partida útil: una forma de determinar rápidamente si una estrategia merece un desarrollo y una optimización más profundos.


Asesor experto

Este script de MetaTrader 5 está diseñado para probar rápidamente si una estrategia comercial puede ser rentable mediante el uso de una combinación de análisis técnico y un modelo de aprendizaje profundo. La idea aquí no es crear un sistema de comercio infalible, sino experimentar combinando indicadores de comercio tradicionales con el poder del aprendizaje automático para ver si hay una ventaja en el mercado.

El script se centra en el par de divisas EURUSD y utiliza el marco temporal H6 (6 horas). Comienza configurando un puñado de indicadores técnicos familiares como el RSI, SMA, PSAR y ATR. Estas son herramientas comunes que utilizan los comerciantes para medir el impulso del mercado, la dirección de la tendencia y la volatilidad. Además, utiliza velas Heikin Ashi, que ayudan a suavizar la acción del precio y hacen que las tendencias sean más fáciles de detectar al reducir el ruido del mercado. Esta configuración inicial sienta las bases de lo que el script intenta lograr: una forma sistemática de buscar oportunidades de compra y venta.

int    handleRSI, handleSMA, handlePSAR, handleATR;
double rsiBuffer[], smaBuffer[], psarBuffer[], atrBuffer[];
double haOpen[], haClose[], haHigh[], haLow[];
CTrade trade;
   handleRSI = iRSI(Symbol, Timeframe, RSIPeriod, PRICE_CLOSE);
   handleSMA = iMA(Symbol, Timeframe, SMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
   handlePSAR = iSAR(Symbol, Timeframe, PSARStep, PSARMaximum);
   handleATR = iATR(Symbol, Timeframe, ATRPeriod);

   if(handleRSI == INVALID_HANDLE || handleSMA == INVALID_HANDLE ||
      handlePSAR == INVALID_HANDLE || handleATR == INVALID_HANDLE)
     {
      Print("Error creating indicators");
      return(INIT_FAILED);
     }

   ArraySetAsSeries(rsiBuffer, true);
   ArraySetAsSeries(smaBuffer, true);
   ArraySetAsSeries(psarBuffer, true);
   ArraySetAsSeries(atrBuffer, true);

   ArrayResize(haOpen, 3);
   ArrayResize(haClose, 3);
   ArrayResize(haHigh, 3);
   ArrayResize(haLow, 3);
   ArraySetAsSeries(haOpen, true);
   ArraySetAsSeries(haClose, true);
   ArraySetAsSeries(haHigh, true);
   ArraySetAsSeries(haLow, true);
   IndicatorRelease(handleRSI);
   IndicatorRelease(handleSMA);
   IndicatorRelease(handlePSAR);
   IndicatorRelease(handleATR);

Lo que distingue a este script es el uso de un modelo de aprendizaje profundo ONNX previamente entrenado. En lugar de confiar únicamente en el análisis técnico tradicional, el script aprovecha el poder predictivo del aprendizaje automático para pronosticar los movimientos de precios. Carga este modelo directamente en el entorno comercial, donde toma datos recientes del mercado, los normaliza y predice si el precio subirá, bajará o se mantendrá igual. Esta predicción se clasifica luego en una de tres categorías: un movimiento ascendente, un movimiento descendente o ningún movimiento significativo en absoluto. Al combinar estas predicciones con indicadores técnicos, el script tiene como objetivo tomar decisiones comerciales más informadas.

#define SAMPLE_SIZE 120

long     ExtHandle=INVALID_HANDLE;
int      ExtPredictedClass=-1;
datetime ExtNextBar=0;
datetime ExtNextDay=0;
float    ExtMin=0.0;
float    ExtMax=0.0;
CTrade   ExtTrade;
int dlsignal=-1;

//--- price movement prediction
#define PRICE_UP   0
#define PRICE_SAME 1
#define PRICE_DOWN 2

#resource "/Files/EURUSD_D1_2024.onnx" as uchar ExtModel[]
//--- create a model from static buffer
   ExtHandle=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
   if(ExtHandle==INVALID_HANDLE)
     {
      Print("OnnxCreateFromBuffer error ",GetLastError());
      return(INIT_FAILED);
     }

//--- since not all sizes defined in the input tensor we must set them explicitly
//--- first index - batch size, second index - series size, third index - number of series (only Close)
   const long input_shape[] = {1,SAMPLE_SIZE,1};
   if(!OnnxSetInputShape(ExtHandle,ONNX_DEFAULT,input_shape))
     {
      Print("OnnxSetInputShape error ",GetLastError());
      return(INIT_FAILED);
     }
//--- check new day
   if(TimeCurrent() >= ExtNextDay)
   {
      GetMinMax();
      //--- set next day time
      ExtNextDay = TimeCurrent();
      ExtNextDay -= ExtNextDay % PeriodSeconds(PERIOD_D1);
      ExtNextDay += PeriodSeconds(PERIOD_D1);
   }

   //--- check new bar
   if(TimeCurrent() < ExtNextBar)
      return;
   
   //--- set next bar time
   ExtNextBar = TimeCurrent();
   ExtNextBar -= ExtNextBar % PeriodSeconds();
   ExtNextBar += PeriodSeconds();
   
   //--- check min and max
   float close = (float)iClose(_Symbol, _Period, 0);
   if(ExtMin > close)
      ExtMin = close;
   if(ExtMax < close)
      ExtMax = close;
void PredictPrice(void)
  {
   static vectorf output_data(1);            // vector to get result
   static vectorf x_norm(SAMPLE_SIZE);       // vector for prices normalize

//--- check for normalization possibility
   if(ExtMin>=ExtMax)
     {
      Print("ExtMin>=ExtMax");
      ExtPredictedClass=-1;
      return;
     }
//--- request last bars
   if(!x_norm.CopyRates(_Symbol,_Period,COPY_RATES_CLOSE,1,SAMPLE_SIZE))
     {
      Print("CopyRates ",x_norm.Size());
      ExtPredictedClass=-1;
      return;
     }
   float last_close=x_norm[SAMPLE_SIZE-1];
//--- normalize prices
   x_norm-=ExtMin;
   x_norm/=(ExtMax-ExtMin);
//--- run the inference
   if(!OnnxRun(ExtHandle,ONNX_NO_CONVERSION,x_norm,output_data))
     {
      Print("OnnxRun");
      ExtPredictedClass=-1;
      return;
     }
//--- denormalize the price from the output value
   float predicted=output_data[0]*(ExtMax-ExtMin)+ExtMin;
//--- classify predicted price movement
   float delta=last_close-predicted;
   if(fabs(delta)<=0.00001)
      ExtPredictedClass=PRICE_SAME;
   else
     {
      if(delta<0)
         ExtPredictedClass=PRICE_UP;
      else
         ExtPredictedClass=PRICE_DOWN;
     }
  }



//+------------------------------------------------------------------+
//| Gets Min and Max values                                          |
//+------------------------------------------------------------------+
void GetMinMax(void)
  {
   vectorf close;
   close.CopyRates(_Symbol,PERIOD_D1,COPY_RATES_CLOSE,0,SAMPLE_SIZE);
   ExtMin=close.Min();
   ExtMax=close.Max();
  }

La lógica comercial es bastante sencilla pero sofisticada. Para una señal de compra (larga), se buscan múltiples condiciones para alinearse: el precio actual debe estar por encima del PSAR y la SMA, el RSI debe estar por debajo de 60 (lo que indica que el mercado no está sobrecomprado) y las velas Heikin Ashi deben sugerir una tendencia alcista. Es fundamental que el modelo de aprendizaje profundo también prediga un movimiento de precios ascendente. Si todos estos factores se alinean, el script abre una posición de compra. De manera similar, busca condiciones opuestas para abrir una posición de venta (corta). Este enfoque en capas tiene como objetivo filtrar el ruido y las señales falsas, asegurándose de que las operaciones solo se realicen cuando varios indicadores y el modelo coincidan en la dirección.

void CalculateHeikinAshi()
{
   double close[], open[], high[], low[];
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);

   int copied = CopyClose(Symbol(), Timeframe, 0, 3, close);
   copied = MathMin(copied, CopyOpen(Symbol(), Timeframe, 0, 3, open));
   copied = MathMin(copied, CopyHigh(Symbol(), Timeframe, 0, 3, high));
   copied = MathMin(copied, CopyLow(Symbol(), Timeframe, 0, 3, low));

   if(copied < 3)
   {
      Print("Not enough data for Heikin Ashi calculation");
      return;
   }

   // Calculate Heikin Ashi values for the last 3 candles
   for(int i = 2; i >= 0; i--)
   {
      haClose[i] = (open[i] + high[i] + low[i] + close[i]) / 4;
      
      if(i == 2)
      {
         haOpen[i] = (open[i] + close[i]) / 2;
      }
      else
      {
         haOpen[i] = (haOpen[i+1] + haClose[i+1]) / 2;
      }
      
      haHigh[i] = MathMax(high[i], MathMax(haOpen[i], haClose[i]));
      haLow[i] = MathMin(low[i], MathMin(haOpen[i], haClose[i]));
   }

   // Debug print
   Print("Heikin Ashi values:");
   for(int i = 0; i < 3; i++)
   {
      Print("Candle ", i, ": Open=", haOpen[i], " High=", haHigh[i], " Low=", haLow[i], " Close=", haClose[i]);
   }
}
   // Copy indicator data
   if(CopyBuffer(handleRSI, 0, 0, 3, rsiBuffer) <= 0 ||
      CopyBuffer(handleSMA, 0, 0, 3, smaBuffer) <= 0 ||
      CopyBuffer(handlePSAR, 0, 0, 3, psarBuffer) <= 0 ||
      CopyBuffer(handleATR, 0, 0, 3, atrBuffer) <= 0)
   {
      Print("Failed to copy indicator data");
      return;
   }

   CalculateHeikinAshi();

   if(ArraySize(haOpen) < 3 || ArraySize(haClose) < 3)
   {
      Print("Not enough Heikin Ashi data");
      return;
   }

   PredictPrice();
   int trend = GetTrend();

   bool longCondition = close > psarBuffer[1] &&
                        close > smaBuffer[1] &&
                        rsiBuffer[1] < 60 &&
                        haClose[1] > haOpen[1] &&
                        haClose[2] > haOpen[2] &&
                        ExtPredictedClass == PRICE_UP &&
                        trend == 1;

   bool shortCondition = close < psarBuffer[1] &&
                         close < smaBuffer[1] &&
                         rsiBuffer[1] > 40 &&
                         haClose[1] < haOpen[1] &&
                         haClose[2] < haOpen[2] &&
                         ExtPredictedClass == PRICE_DOWN &&
                         trend == -1;

   // Debug printing
   Print("--- Debug Info ---");
   Print("Close: ", close, " PSAR: ", psarBuffer[1], " SMA: ", smaBuffer[1]);
   Print("RSI: ", rsiBuffer[1], " HA Close[1]: ", haClose[1], " HA Open[1]: ", haOpen[1]);
   Print("HA Close[2]: ", haClose[2], " HA Open[2]: ", haOpen[2]);
   Print("ExtPredictedClass: ", ExtPredictedClass, " Trend: ", trend);
   Print("Long Condition: ", longCondition, " Short Condition: ", shortCondition);

   if(longCondition)
   {
      Print("Long Condition Met");
      if(PositionsTotal() == 0)
      {
         double atrStopLoss = SLATRMultiple * atrBuffer[1];
         double minStopLoss = GetMinimumStopLoss();
         double sl = close - MathMax(atrStopLoss, minStopLoss);
         double tp = close + TPATRMultiple * atrBuffer[1];
         
         bool result = trade.Buy(0.01, Symbol(), 0, sl, tp);
         if(result)
            Print("Buy order placed successfully. SL: ", sl, " TP: ", tp);
         else
            Print("Failed to place buy order. Error: ", GetLastError());
      }
      else
      {
         Print("Position already open. Skipping buy order.");
      }
   }
   else if(shortCondition)
   {
      Print("Short Condition Met");
      if(PositionsTotal() == 0)
      {
         double atrStopLoss = SLATRMultiple * atrBuffer[1];
         double minStopLoss = GetMinimumStopLoss();
         double sl = close + MathMax(atrStopLoss, minStopLoss);
         double tp = close - TPATRMultiple * atrBuffer[1];
         
         bool result = trade.Sell(0.01, Symbol(), 0, sl, tp);
         if(result)
            Print("Sell order placed successfully. SL: ", sl, " TP: ", tp);
         else
            Print("Failed to place sell order. Error: ", GetLastError());
      }
      else
      {
         Print("Position already open. Skipping sell order.");
      }
   }
   
   ManageTrailingStop();
}

La gestión de riesgos también está integrada en el script. Utiliza el rango verdadero promedio (ATR) para establecer niveles dinámicos de stop-loss y take-profit. Al basar estos niveles en la volatilidad del mercado, el script se adapta a las condiciones cambiantes del mercado, con el objetivo de protegerse contra pérdidas significativas y, al mismo tiempo, fijar ganancias. Además, incluye un mecanismo de trailing stop, que ajusta el stop-loss a medida que la operación se mueve en una dirección favorable, protegiendo aún más las ganancias.

double GetMinimumStopLoss()
{
   double spread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) * SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   double minStopLevel = SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL) * SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   return MathMax(spread * 2, minStopLevel); // Usamos el doble del spread como mínimo
}

// Agregar esta función para manejar el trailing stop
void ManageTrailingStop()
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      if(PositionSelectByTicket(PositionGetTicket(i)))
      {
         if(PositionGetString(POSITION_SYMBOL) == Symbol())
         {
            double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double currentStopLoss = PositionGetDouble(POSITION_SL);
            double currentTakeProfit = PositionGetDouble(POSITION_TP);
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
               double newStopLoss = NormalizeDouble(SymbolInfoDouble(Symbol(), SYMBOL_BID) - TrailingStop * SymbolInfoDouble(Symbol(), SYMBOL_POINT), Digits());
               if(newStopLoss > currentStopLoss + TrailingStep * SymbolInfoDouble(Symbol(), SYMBOL_POINT))
               {
                  if(trade.PositionModify(PositionGetTicket(i), newStopLoss, currentTakeProfit))
                  {
                     Print("Trailing stop ajustado para posición larga. Nuevo SL: ", newStopLoss);
                  }
               }
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
               double newStopLoss = NormalizeDouble(SymbolInfoDouble(Symbol(), SYMBOL_ASK) + TrailingStop * SymbolInfoDouble(Symbol(), SYMBOL_POINT), Digits());
               if(newStopLoss < currentStopLoss - TrailingStep * SymbolInfoDouble(Symbol(), SYMBOL_POINT))
               {
                  if(trade.PositionModify(PositionGetTicket(i), newStopLoss, currentTakeProfit))
                  {
                     Print("Trailing stop ajustado para posición corta. Nuevo SL: ", newStopLoss);
                  }
               }
            }
         }
      }
   }
}

En resumen, este script sirve como una forma rápida y experimental de probar la viabilidad de una estrategia comercial. No pretende ser la solución perfecta, sino más bien un campo de pruebas para ver si la combinación de análisis técnico y un modelo de aprendizaje profundo puede identificar oportunidades comerciales rentables. Si bien puede que no garantice ganancias, proporciona un enfoque más matizado e informado para las decisiones comerciales, combinando la intuición humana con conocimientos de aprendizaje automático.


Resultados

Ajustes

Entradas

Pruebas retrospectivas

Gráfico EA

Los resultados del Asesor Experto (EA) brindan una visión detallada de su desempeño durante el período de prueba retrospectiva. Partiendo de un depósito inicial de 10.000 dólares, la estrategia obtuvo un modesto beneficio neto total de 17 unidades, lo que indica que, si bien generó beneficios, éstos fueron relativamente pequeños en proporción a la inversión inicial. La curva de equilibrio refleja este crecimiento gradual, mostrando una trayectoria ascendente constante, aunque lenta, a lo largo del tiempo.

Una de las métricas destacadas aquí es el factor de beneficio de 4,39. Se trata de una cifra sólida, que sugiere que por cada unidad de riesgo asumida, la estrategia obtuvo 4,39 unidades de recompensa. Esto implica que el EA fue eficaz al maximizar las ganancias en comparación con sus pérdidas. El factor de recuperación de 4,48 respalda aún más esto, mostrando que la estrategia fue capaz de recuperarse de las caídas de manera efectiva, lo que es una señal positiva de su solidez. Sin embargo, vale la pena destacar el ratio de Sharpe, que se sitúa en 5,25. Si bien esta relación es relativamente alta y generalmente indica buenos retornos ajustados al riesgo, la pequeña ganancia general sugiere que la estrategia podría haber estado asumiendo riesgos mínimos, lo que llevó a la ganancia absoluta limitada.

Profundizando en las estadísticas comerciales, el EA ejecutó 16 operaciones en total, divididas equitativamente entre posiciones largas y cortas. La tasa de ganancia para operaciones largas fue mayor, 62,5%, mientras que las operaciones cortas tuvieron una tasa de ganancia del 37,5%. Esta disparidad indica que la estrategia tuvo más éxito a la hora de capturar los movimientos ascendentes del mercado. Curiosamente, las operaciones rentables constituyeron exactamente el 50% de las operaciones totales, lo que resalta que la rentabilidad del EA no se debió a una alta tasa de ganancias, sino más bien al tamaño de sus ganadores versus sus perdedores. Las operaciones con ganancias más grandes y promedio fueron positivas, lo que sugiere que el EA logró asegurar ganancias cuando el mercado se movió favorablemente. La curva de equilibrio refleja estos resultados, mostrando períodos de caída seguidos de ganancias que indican un enfoque cuidadoso pero constante hacia el trading.

Las métricas de reducción muestran que el EA mantuvo un bajo nivel de exposición al riesgo. La caída absoluta del saldo fue de 2 unidades, lo cual es bastante pequeño, y la caída relativa fue de solo 0,02%. Esta reducción tan baja sugiere que la estrategia del EA fue conservadora y priorizó la preservación del capital por sobre la búsqueda agresiva de ganancias. Este enfoque conservador puede ser beneficioso en mercados volátiles, pero también explica el beneficio neto total relativamente modesto.

En resumen, el EA demostró un enfoque cauteloso y sistemático hacia el trading, centrándose en mantener un alto factor de ganancia y minimizar las caídas. Si bien el beneficio total fue relativamente pequeño, el alto factor de beneficio y las bajas reducciones indican una estrategia que se centra más en la gestión de riesgos que en la generación rápida de beneficios. Esto hace que el EA sea adecuado para operadores que priorizan un crecimiento constante y de bajo riesgo por sobre estrategias más agresivas y de alto riesgo. Sin embargo, para aquellos que buscan retornos absolutos más altos, el EA podría necesitar ajustes o mejoras adicionales para aumentar su potencial de ganancias y al mismo tiempo mantener su sólido marco de gestión de riesgos.


Conclusión

La exploración de la integración de modelos de aprendizaje profundo con el análisis técnico tradicional ha mostrado resultados prometedores. A través del script de Python, vimos cómo un enfoque sistemático que utiliza datos históricos, indicadores clave y aprendizaje automático puede ayudar a identificar posibles oportunidades comerciales. Los resultados fueron modestos pero consistentes, demostrando que dicha combinación puede producir un resultado positivo, aunque con algunas limitaciones. De manera similar, el Asesor Experto (EA) de MetaTrader 5 confirmó este enfoque cauteloso. Si bien el desempeño del EA durante las pruebas retrospectivas reveló un crecimiento pequeño pero constante con una reducción mínima, destacó una estrategia centrada en la gestión del riesgo en lugar de maximizar las ganancias. Esta naturaleza conservadora lo hace atractivo para los traders que priorizan un crecimiento constante y de bajo riesgo. Sin embargo, quienes buscan obtener mayores retornos necesitan mayor refinamiento y optimización. Al final, estas herramientas sirven como base, un punto de partida para que los traders combinen la intuición humana con el poder analítico de la IA, ofreciendo un enfoque matizado para navegar por el complejo mundo del trading de divisas.

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

Archivos adjuntos |
EURUSD_D1_2024.onnx (884.41 KB)
004.py (8.06 KB)
PHD_Final.mq5 (27.7 KB)
005.py (8.14 KB)
Redes neuronales en el trading: Detección de objetos con reconocimiento de escena (HyperDet3D) Redes neuronales en el trading: Detección de objetos con reconocimiento de escena (HyperDet3D)
Le proponemos que conozca un nuevo enfoque de la detección de objetos mediante hiper-redes: una hiper-red de generación de coeficientes de peso para el modelo básico que permite tener en cuenta las peculiaridades del estado actual del mercado. Este enfoque mejora la precisión de las previsiones adaptando el modelo a las distintas condiciones comerciales.
Algoritmo de tiro con arco - Archery Algorithm (AA) Algoritmo de tiro con arco - Archery Algorithm (AA)
Este artículo detalla un algoritmo de optimización inspirado en el tiro con arco, centrado en el uso del método de la ruleta como mecanismo de selección de zonas prometedoras para las "flechas". Este método nos permite evaluar la calidad de las soluciones y seleccionar las más prometedoras para seguir estudiándolas.
Del básico al intermedio: Unión (I) Del básico al intermedio: Unión (I)
En este artículo, veremos qué es una unión. Aquí, mediante la experimentación, analizaremos las primeras construcciones en las que podría utilizarse una unión. No obstante, lo que se mostrará aquí es solo la parte básica de todo un conjunto de conceptos e información que se explorará más a fondo en artículos futuros. El contenido expuesto aquí tiene un propósito puramente didáctico. En ningún caso debe considerarse una aplicación cuya finalidad no sea aprender y estudiar los conceptos mostrados.
Soluciones sencillas para trabajar cómodamente con indicadores Soluciones sencillas para trabajar cómodamente con indicadores
En este artículo le contaremos cómo crear un panel simple para cambiar la configuración del indicador directamente desde el gráfico, y qué cambios se deberán introducir en el indicador para conectar este panel. Este artículo está dirigido exclusivamente a aquellos que acaban de empezar a familiarizarse con MQL5.