English Русский 中文 Deutsch 日本語
preview
Aprendizaje automático y Data Science (Parte 45): Predicción de series temporales en Forex con el modelo Prophet de Facebook

Aprendizaje automático y Data Science (Parte 45): Predicción de series temporales en Forex con el modelo Prophet de Facebook

MetaTrader 5Integración |
17 0
Omega J Msigwa
Omega J Msigwa

Contenido


¿Qué es un modelo «Prophet»?

El modelo Prophet es una herramienta de código abierto para la predicción de series temporales desarrollada por Meta (antes Facebook). Está diseñado para ofrecer previsiones precisas y fáciles de usar con fines empresariales y analíticos, especialmente para datos de series temporales con una marcada estacionalidad y tendencias.

Este modelo fue presentado por Facebook (S. J. Taylor y Benjamin Letham, 2018), concebido originalmente para la previsión de datos diarios con estacionalidad semanal y anual, además de los efectos de los días festivos. Posteriormente se amplió para abarcar más tipos de datos estacionales. Funciona mejor con series temporales que presentan una marcada estacionalidad y que cuentan con datos históricos de varias temporadas.

Términos habituales:

  • Tendencia: La tendencia muestra la dirección general de los datos a largo plazo y elimina las variaciones estacionales. 
  • Estacionalidad: La estacionalidad se refiere a las variaciones que se producen en un periodo breve y que no son lo suficientemente marcadas como para considerarse una «tendencia». 

En este artículo, vamos a analizar e implementar este modelo a partir de datos del mercado de divisas y veremos cómo puede ayudarnos a superar al mercado; pero, antes de nada, dediquemos un momento a comprender este modelo en detalle.


Comprender el modelo «Prophet»

El modelo Prophet puede considerarse un modelo de regresión no lineal que viene dado por la fórmula:

  

Figura 1

Donde: 

  •   describe una tendencia lineal por tramos (o «término de crecimiento»).
  •  describe los distintos patrones estacionales.  
  •  recoge los efectos de calendario, y es un término de error de ruido blanco.

01: El componente de tendencia

El componente de tendencia admite puntos de cambio, que se seleccionan automáticamente si no se especifican manualmente. Estos puntos de cambio representan momentos en los que la tendencia puede cambiar (por ejemplo, un crecimiento o un descenso repentino).

También puedes utilizar, si lo deseas, el modelo de crecimiento logístico en lugar del lineal, lo que introduce un parámetro de capacidad (cap) para modelar los efectos de saturación. Esto resulta útil cuando el crecimiento se ralentiza tras alcanzar un determinado límite natural.

02: Estacionalidad

En el modelo Prophet, la estacionalidad se modela mediante series de Fourier.

Por defecto:

  • Se utiliza un orden de 10 para la estacionalidad anual.
  • Se utiliza un orden 3 para la estacionalidad semanal.

Estos términos de Fourier ayudan al modelo a capturar los efectos estacionales recurrentes.

03: La parte de efectos de días festivos

Los efectos de calendario se incorporan como variables indicadoras (codificadas con el método «one-hot»), lo que permite al modelo ajustar su previsión en torno a fechas especiales que, históricamente, han provocado desviaciones en el comportamiento. Por ejemplo, noticias económicas o días festivos.

El modelo completo se estima utilizando un enfoque bayesiano, lo que permite la selección automática de los puntos de cambio y otros parámetros del modelo.

Aunque este modelo aditivo de descomposición básica parece sencillo, el cálculo de los términos que componen esta fórmula es de gran complejidad matemática, por lo que, si no tienes ni idea de lo que estás haciendo, este modelo puede dar lugar a previsiones erróneas.

Prophet nos ofrece dos enfoques de modelización.

  1. Modelo de crecimiento lineal por tramos (predeterminado)
  2. Modelo de crecimiento logístico

01. Modelo lineal por tramos

Este es el modelo predeterminado que utiliza Prophet. Se parte de la hipótesis de que la tendencia de los datos sigue una trayectoria lineal, pero que puede cambiar en momentos concretos (denominados «puntos de cambio»). Este modelo es adecuado para datos que presentan patrones de crecimiento o descenso constantes, aunque puedan presentar cambios bruscos.

Este enfoque de modelización es el que se representa mediante la fórmula de la Figura 1.

02. Modelo de crecimiento logístico

Este modelo es adecuado para datos que muestran un crecimiento saturado, es decir, que crecen rápidamente al principio, pero se ralentizan a medida que se acercan a una capacidad máxima o a un límite. Este tipo de patrón se observa a menudo en sistemas del mundo real con límites naturales o impuestos por el negocio (como la aceptación por parte de los usuarios en un mercado saturado).

El modelo de crecimiento logístico incorpora un parámetro de capacidad que define este límite superior.

Este enfoque de modelización viene dado por la siguiente fórmula:

 

Figura 2

Donde:

 es la capacidad de carga, es la tasa de crecimiento y es un parámetro de desplazamiento.


Implementación del modelo «Prophet» en Python

Utilizando los datos del EURUSD del gráfico horario, intentemos detectar la tendencia y la estacionalidad, y pronosticar los valores futuros mediante este modelo.

Lo primero que tienes que hacer es instalar todas las dependencias del archivo requirements.txtque se adjunta al final de este artículo

pip install -r requirements.txt

Importaciones:

import pandas as pd
import numpy as np
import MetaTrader5 as mt5
import matplotlib.pyplot as plt
import seaborn as sns
from prophet import Prophet

plt.style.use('fivethirtyeight') 
sns.set_style("darkgrid")

Recopilemos los datos de MetaTrader 5.

if not mt5.initialize(r"c:\Program Files\MetaTrader 5 IC Markets (SC)\terminal64.exe"):
    print("Failed to initialize MetaTrader5. Error = ",mt5.last_error())
    mt5.shutdown()      

symbol = "EURUSD"
timeframe = mt5.TIMEFRAME_H1

rates = mt5.copy_rates_from_pos(symbol, timeframe, 1, 10000)

if rates is None:
    print(f"Failed to copy rates for symbol={symbol}. MT5 Error = {mt5.last_error()}")

El modelo «Prophet» se basa en gran medida en la función «datetime» o «date stamp». Esta característica es imprescindible para que este modelo funcione. 

Tras recibir los datos (cotizaciones) de MetaTrader 5, los convertimos en un objeto DataFrame de Pandas. A continuación, convertimos la columna «time», que contiene el tiempo en segundos, a un formato de fecha y hora.

rates_df = pd.DataFrame(rates) # we convert rates object to a dataframe
rates_df["time"] = pd.to_datetime(rates_df["time"], unit="s") # we convert the time from seconds to datatime

rates_df

Resultados:

time open high low close tick_volume spread real_volume
0 2023-11-10 23:00:00 1.06849 1.06873 1.06826 1.06846 762 0 0
1 2023-11-13 00:00:00 1.06828 1.06853 1.06779 1.06841 1059 10 0
2 2023-11-13 01:00:00 1.06854 1.06907 1.06854 1.06906 571 0 0
3 2023-11-13 02:00:00 1.06904 1.06904 1.06822 1.06839 1053 0 0
4 2023-11-13 03:00:00 1.06840 1.06886 1.06811 1.06867 1204 0 0


El modelo Prophet es un modelo univariado que solo requiere dos características para funcionar a partir de un DataFrame de Pandas, es decir, la característica de fecha y hora denominada «ds» (date stamp) y la variable objetivo marcada como «y» en el DataFrame.

Por ahora, vamos a crear un conjunto de datos sencillo a partir del recibido de MetaTrader 5 con dos características (tiempo y volatilidad). Estos son los datos que vamos a aplicar más adelante al modelo «Prophet».

prophet_df = pd.DataFrame({
    "time": rates_df["time"],
    "volatility": rates_df["high"] - rates_df["low"]
}).set_index("time")

prophet_df

La volatilidad (calculada como la diferencia entre el precio máximo y el mínimo) es nuestra variable objetivo.

A diferencia de otros modelos de predicción de series temporales, como ARIMA y VAR (analizados anteriormente), que requieren que la variable objetivo sea estacionaria, el modelo Prophet no está sujeto a esta condición. También puede funcionar con datos no estacionarios, pero todos los modelos de aprendizaje automático suelen ofrecer buenos resultados con variables estacionarias, ya que, por su propia naturaleza (tienen media, varianza y desviación estándar constantes), a los modelos les resulta más fácil aprender de ellas.

Para este modelo, he optado por trabajar con una variable objetivo estacionaria para facilitarnos mucho el trabajo.

Representemos gráficamente el DataFrame y observemos las características.

# Color pallete for plotting
color_pal = ["#F8766D", "#D39200", "#93AA00",
             "#00BA38", "#00C19F", "#00B9E3",
             "#619CFF", "#DB72FB"]

prophet_df.plot(figsize=(7,5), color=color_pal, title="Volatility (high-low) against time", ylabel="volatility", xlabel="time")

Resultados:

Figura 3

Si lo deseamos, podemos crear variables X e y para evaluar el impacto de las variables temporales en la volatilidad del mercado.

def create_features(df, label=None):
    """
    Creates time series features from datetime index.
    """
    
    df = df.copy()
    df['date'] = df.index
    df['hour'] = df['date'].dt.hour
    df['dayofweek'] = df['date'].dt.dayofweek
    df['quarter'] = df['date'].dt.quarter
    df['month'] = df['date'].dt.month
    df['year'] = df['date'].dt.year
    df['dayofyear'] = df['date'].dt.dayofyear
    df['dayofmonth'] = df['date'].dt.day
    df['weekofyear'] = df['date'].dt.isocalendar().week
    
    X = df[['hour','dayofweek','quarter','month','year',
           'dayofyear','dayofmonth','weekofyear']]
    if label:
        y = df[label]
        return X, y
    return X

X, y = create_features(prophet_df, label='volatility')

features_and_target = pd.concat([X, y], axis=1)

Resultados:

hour dayofweek quarter month year dayofyear dayofmonth weekofyear volatility
time
2023-11-13 16:00:00 16 0 4 11 2023 317 13 46 0.00122
2023-11-13 17:00:00 17 0 4 11 2023 317 13 46 0.00179
2023-11-13 18:00:00 18 0 4 11 2023 317 13 46 0.00186
2023-11-13 19:00:00 19 0 4 11 2023 317 13 46 0.00125
2023-11-13 20:00:00 20 0 4 11 2023 317 13 46 0.00150


Podemos representar gráficamente estas características en función de la volatilidad para realizar un análisis manual.

sns.pairplot(features_and_target.dropna(),
             hue='hour',
             x_vars=['hour','dayofweek',
                     'year','weekofyear'],
             y_vars='volatility',
             height=5,
             plot_kws={'alpha':0.45, 'linewidth':0.5}
            )

plt.suptitle(f"{symbol} close prices by Hour, Day of Week, Year, and Week")
plt.show()

Resultados:


Figura 4

Como se puede observar en los subgráficos, la hora, el día de la semana, el año y la semana del año influyen en la volatilidad que se produce en cada hora del gráfico. Saber esto nos da la confianza necesaria para seguir adelante y utilizar estos datos en el modelo «Prophet».

Entrenamiento del modelo «Prophet»

Empezamos dividiendo los datos en conjuntos de entrenamiento y de prueba utilizando una fecha concreta.

split_date = '01-Jan-2025' # threshold date between training and testing samples, all values after this date are for testing 

prophet_df_train = prophet_df.loc[prophet_df.index <= split_date].copy().reset_index().rename(columns={"time": "ds", "volatility": "y"})
prophet_df_test = prophet_df.loc[prophet_df.index > split_date].copy().reset_index().rename(columns={"time": "ds", "volatility": "y"})

Entrenamos el modelo «Prophet» con los datos de entrenamiento.

model = Prophet()
model.fit(prophet_df_train)

Una vez entrenado el modelo, a menudo queremos comprobar su eficacia con los datos fuera de muestra, es decir, la información que el modelo no ha visto antes. A diferencia de otros modelos, el modelo Prophet presenta una forma ligeramente diferente de generar las previsiones.

test_fcst = model.predict(df=prophet_df_test)

En lugar de devolver un vector con las predicciones, este modelo devuelve un DataFrame completo que contiene diversas características que representan las predicciones y el estado del modelo.

test_fcst.head()

Resultados:

	ds      trend   	yhat_lower      yhat_upper      trend_lower     trend_upper     additive_terms  additive_terms_lower    additive_terms_upper    daily   daily_lower     daily_upper     weekly  weekly_lower    weekly_upper    multiplicative_terms    multiplicative_terms_lower      multiplicative_terms_upper      yhat
0       2025-01-02 00:00:00     0.001674        0.000168        0.001993        0.001674        0.001674        -0.000571       -0.000571       -0.000571       -0.000510       -0.000510       -0.000510       -0.000061       -0.000061       -0.000061       0.0     0.0     0.0     0.001102
1       2025-01-02 01:00:00     0.001674        0.000161        0.001977        0.001674        0.001674        -0.000614       -0.000614       -0.000614       -0.000556       -0.000556       -0.000556       -0.000057       -0.000057       -0.000057       0.0     0.0     0.0     0.001060
2       2025-01-02 02:00:00     0.001674        0.000337        0.002123        0.001674        0.001674        -0.000483       -0.000483       -0.000483       -0.000430       -0.000430       -0.000430       -0.000054       -0.000054       -0.000054       0.0     0.0     0.0     0.001191

La siguiente tabla recoge el significado de algunas de las columnas (características) que devuelve el método predict.

Columna Significado
ds La fecha y hora (marca de tiempo) del punto previsto.
yhat El valor previsto final (lo que Prophet predice en ese momento).
yhat_lower, yhat_upper Los límites inferior y superior del intervalo de confianza del 80 % (o del 95 %) para yhat.
trend El valor del componente de tendencia en el instante ds (por ejemplo, un crecimiento lento o un descenso a lo largo del tiempo).
trend_lower, trend_upper Intervalo de confianza del componente de tendencia.
additive_terms La suma de todos los componentes estacionales y festivos en el momento ds (por ejemplo, diarios, semanales y festivos).
additive_terms_lower, additive_terms_upper Límites para los componentes aditivos.
daily El efecto de estacionalidad diario (por ejemplo, los patrones por horas a lo largo del día).
daily_lower, daily_upper Intervalo de confianza para el componente diario.
weekly El efecto de estacionalidad semanal (por ejemplo, los fines de semana difieren de los días laborables)
weekly_lower, weekly_upper Intervalo de confianza para el componente semanal.

Lo que más necesitamos son yhat, yhat_lower, yhat_upper, trend, los patrones de estacionalidad (diarios, semanales, anuales), días festivos (si se incluyen) y los límites de error de los componentes (*_lower y *_upper).

Representemos gráficamente los valores reales y las previsiones de la muestra de prueba, junto con los valores reales de la muestra de entrenamiento.

f, ax = plt.subplots(figsize=(7,5))
ax.scatter(prophet_df_test["ds"], prophet_df_test['y'], color='r') # plot actual values from the testing sample in red

fig = model.plot(test_fcst, ax=ax) # plot the forecasts 

Figura de resultados:

Figura 5

Los valores en negro representan la muestra de entrenamiento, los de color rojo son los valores reales de la muestra de prueba y los de color azul son las predicciones realizadas por el modelo para la muestra de prueba.

Es difícil comprender la eficacia del modelo con solo observar este gráfico. Creemos unos pequeños gráficos que representen los datos reales y las previsiones de la muestra de prueba.

Evaluemos el modelo en enero de 2025, el primer mes de los datos de prueba.

f, ax = plt.subplots(figsize=(7, 5))

ax.scatter(prophet_df_test["ds"], prophet_df_test['y'], color='r')
fig = model.plot(test_fcst, ax=ax)
ax.set_xbound(
            lower=pd.to_datetime("2025-01-01"), # starting data on the x axis
            upper=pd.to_datetime("2025-02-01")) # ending data on the x axis

ax.set_ylim(0, 0.005)
plot = plt.suptitle("January 2025, Actual vs Forecasts")

Resultados:


Figura 6

Según lo que vemos en la imagen anterior, el modelo «Prophet» acierta en algunas predicciones, pero no parece dar buenos resultados con los valores atípicos de los datos.

Si lo deseamos, podemos profundizar en las predicciones analizando los valores reales y comparándolos con las predicciones realizadas por el modelo durante la primera semana de enero (del 1 al 8 de enero).

f, ax = plt.subplots(figsize=(9, 5))

ax.scatter(prophet_df_test["ds"], prophet_df_test['y'], color='r')
fig = model.plot(test_fcst, ax=ax)
ax.set_xbound(
            lower=pd.to_datetime("2025-01-01"),
            upper=pd.to_datetime("2025-01-08"))

ax.set_ylim(0, 0.005)
plot = plt.suptitle("January 01-08, 2025. Actual vs Forecasts")

Resultados:

Figura 7

Queda mucho mejor. Sin embargo, aunque el modelo parece captar algunos patrones, sus previsiones (predicciones) no se acercan demasiado a los valores reales, algo que a menudo nos esforzamos por conseguir cuando utilizamos modelos de regresión.

Sin embargo, parece que ofrece una buena predicción generalizada.

Analicémoslo utilizando algunos indicadores de evaluación.

import sklearn.metrics as metric

def forecast_accuracy(forecast, actual):
    # Convert to numpy arrays if they aren't already
    forecast = np.asarray(forecast)
    actual = np.asarray(actual)
    
    metrics = {
        'mape': metric.mean_absolute_percentage_error(actual, forecast),
        'me': np.mean(forecast - actual),  # Mean Error
        'mae': metric.mean_absolute_error(actual, forecast),
        'mpe': np.mean((forecast - actual) / actual),  # Mean Percentage Error
        'rmse': metric.root_mean_squared_error(actual, forecast),
        'minmax': 1 - np.mean(np.minimum(forecast, actual) / np.maximum(forecast, actual)),
        "r2_score": metric.r2_score(forecast, actual)
    }
    return metrics

results = forecast_accuracy(test_pred, prophet_df_test["y"])

for metric_name, value in results.items():
    print(f"{metric_name:<10}: {value:.6f}")

Resultados:

mape      : 0.603277
me        : 0.000130
mae       : 0.000829
mpe       : 0.430299
rmse      : 0.001221
minmax    : 0.339292
r2_score  : -4.547775

Lo que me interesa es la métrica MAPE (error porcentual absoluto medio); un valor de aproximadamente 0,6 significa que, de media, las previsiones realizadas por el modelo se desvían un 60 % de los valores reales. En pocas palabras, el modelo hizo predicciones pésimas y es propenso al error.


Añadir días festivos al modelo «Prophet»

El modelo «Prophet» está diseñado para tener en cuenta el hecho de que, en cualquier conjunto de datos, pueden producirse eventos que provoquen cambios inusuales en una serie temporal; a estos eventos los denominamos «días festivos».

En la vida real, es probable que los días festivos provoquen fluctuaciones en los datos empresariales; estas pueden ser:

  • Días festivos (por ejemplo, Año Nuevo, Navidad)
  • Eventos empresariales (por ejemplo, el Black Friday, el lanzamiento de un producto)
  • Acontecimientos financieros (por ejemplo, comunicados del banco central, cierres de trimestre)
  • Acontecimientos locales (por ejemplo, elecciones, fenómenos meteorológicos extremos)

Estos eventos no siguen un patrón estacional regular, aunque sí se repiten, a menudo cada año, cada trimestre, cada día, etc.

En los datos financieros de trading, podemos considerar las noticias económicas como días festivos, ya que provocan la situación descrita. De este modo, podríamos ayudar a nuestro modelo a resolver su problema actual: la incapacidad de captar estos valores extremos.

Como se puede observar en la Figura 1, que recoge la fórmula del modelo Prophet, al añadir los días festivos si los hay, el modelo queda completo, ya que los días festivos son uno de los elementos fundamentales de la fórmula.

Dicho esto, tenemos que recopilar las noticias utilizando el lenguaje MQL5.

Nombre del archivo: OHLC + News.mq5

input datetime start_date = D'01.01.2023';
input datetime end_date = D'24.6.2025';
input ENUM_TIMEFRAMES timeframe = PERIOD_H1;

MqlRates rates[];
struct news_data_struct
  {   
    datetime time[]; //News release time
    double open[]; //Candle opening price
    double high[]; //Candle high price
    double low[]; //Candle low price
    double close[]; //Candle close price
    string name[]; //Name of the news
    ENUM_CALENDAR_EVENT_SECTOR sector[]; //The sector a news is related to
    ENUM_CALENDAR_EVENT_IMPORTANCE importance[]; //Event importance
    double actual[]; //actual value
    double forecast[]; //forecast value
    double previous[]; //previous value
    
    void Resize(uint size)
      {
          ArrayResize(time, size);
          ArrayResize(open, size);
          ArrayResize(high, size);
          ArrayResize(low, size);
          ArrayResize(close, size);
          ArrayResize(name, size);
          ArrayResize(sector, size);
          ArrayResize(importance, size);
          ArrayResize(actual, size);
          ArrayResize(forecast, size);
          ArrayResize(previous, size);
      }
    
  } news_data;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---

   if (!ChartSetSymbolPeriod(0, Symbol(), timeframe))
      return;
   
   SaveNews(StringFormat("%s.%s.OHLC + News.csv",Symbol(),EnumToString(timeframe)));
  }
//+------------------------------------------------------------------+
//|                                                                  |
//|   The function which collects news alongsided OHLC values and    |
//|   saves the data to a CSV file                                   |
//|                                                                  |
//+------------------------------------------------------------------+
void SaveNews(string csv_name)
 {
//--- get OHLC values first
   
   ResetLastError();
   if (CopyRates(Symbol(), timeframe, start_date, end_date, rates)<=0)
     {
       printf("%s failed to get price information from %s to %s. Error = %d",__FUNCTION__,string(start_date),string(end_date),GetLastError());
       return;
     }
      
   uint size = rates.Size();   
   news_data.Resize(size-1);

//---

   FileDelete(csv_name); //Delete an existing csv file of a given name
   int csv_handle = FileOpen(csv_name,FILE_WRITE|FILE_SHARE_WRITE|FILE_CSV|FILE_ANSI|FILE_COMMON,",",CP_UTF8); //csv handle
   
   if(csv_handle == INVALID_HANDLE)
     {
       printf("Invalid %s handle Error %d ",csv_name,GetLastError());
       return; //stop the process
     }
     
   FileSeek(csv_handle,0,SEEK_SET); //go to file begining
   FileWrite(csv_handle,"Time,Open,High,Low,Close,Name,Sector,Importance,Actual,Forecast,Previous"); //write csv header
   
   MqlCalendarValue values[]; //https://www.mql5.com/en/docs/constants/structures/mqlcalendar#mqlcalendarvalue
   for (uint i=0; i<size-1; i++)
      {
         news_data.time[i] = rates[i].time;
         news_data.open[i] = rates[i].open;
         news_data.high[i] = rates[i].high;
         news_data.low[i] = rates[i].low;
         news_data.close[i] = rates[i].close;
         
         int all_news = CalendarValueHistory(values, rates[i].time, rates[i+1].time, NULL, NULL); //we obtain all the news with their values https://www.mql5.com/en/docs/calendar/calendarvaluehistory
         
         for (int n=0; n<all_news; n++)
            {
              MqlCalendarEvent event;
              CalendarEventById(values[n].event_id, event); //Here among all the news we select one after the other by its id https://www.mql5.com/en/docs/calendar/calendareventbyid
                   
              MqlCalendarCountry country; //The couhtry where the currency pair originates
              CalendarCountryById(event.country_id, country); //https://www.mql5.com/en/docs/calendar/calendarcountrybyid
                 
              if (StringFind(Symbol(), country.currency)>-1) //We want to ensure that we filter news that has nothing to do with the base and the quote currency for the current symbol pair
                { 
                     news_data.name[i] = event.name;  
                     news_data.sector[i] = event.sector;
                     news_data.importance[i] = event.importance;
                       
                     news_data.actual[i] = !MathIsValidNumber(values[n].GetActualValue()) ? 0 : values[n].GetActualValue();
                     news_data.forecast[i] = !MathIsValidNumber(values[n].GetForecastValue()) ? 0 : values[n].GetForecastValue();
                     news_data.previous[i] = !MathIsValidNumber(values[n].GetPreviousValue()) ? 0 : values[n].GetPreviousValue();
                }
            }
          
          FileWrite(csv_handle,StringFormat("%s,%f,%f,%f,%f,%s,%s,%s,%f,%f,%f",
                                 (string)news_data.time[i],
                                 news_data.open[i],
                                 news_data.high[i],
                                 news_data.low[i],
                                 news_data.close[i],
                                 news_data.name[i],
                                 EnumToString(news_data.sector[i]),
                                 EnumToString(news_data.importance[i]),
                                 news_data.actual[i],
                                 news_data.forecast[i],
                                 news_data.previous[i]
                               ));
       }  
//---

   FileClose(csv_handle);
 }

Tras recopilar las noticias dentro de la función SaveNews, los datos obtenidos se guardan en un archivo CSV en la «ruta común» (carpeta).

Dentro del script de Python, cargamos estos datos desde la misma ruta.

from Trade.TerminalInfo import CTerminalInfo
import os

terminal = CTerminalInfo()
data_path = os.path.join(terminal.common_data_path(), "Files")

timeframe = "PERIOD_H1"
df = pd.read_csv(os.path.join(data_path, f"{symbol}.{timeframe}.OHLC + News.csv"))

df

Resultados:

Time Open High Low Close Name Sector Importance Actual Forecast Previous
0 2023.01.02 01:00:00 1.06967 1.06983 1.06927 1.06983 New Year's Day CALENDAR_SECTOR_HOLIDAYS CALENDAR_IMPORTANCE_NONE 0.0 0.0 0.0
1 2023.01.02 02:00:00 1.06984 1.07059 1.06914 1.07041 New Year's Day CALENDAR_SECTOR_HOLIDAYS CALENDAR_IMPORTANCE_NONE 0.0 0.0 0.0
2 2023.01.02 03:00:00 1.07059 1.07069 1.06858 1.06910 New Year's Day CALENDAR_SECTOR_HOLIDAYS CALENDAR_IMPORTANCE_NONE 0.0 0.0 0.0
3 2023.01.02 04:00:00 1.06909 1.06909 1.06828 1.06880 New Year's Day CALENDAR_SECTOR_HOLIDAYS CALENDAR_IMPORTANCE_NONE 0.0 0.0 0.0
4 2023.01.02 05:00:00 1.06881 1.07029 1.06880 1.06897 New Year's Day CALENDAR_SECTOR_HOLIDAYS CALENDAR_IMPORTANCE_NONE 0.0 0.0 0.0


Dado que hemos recopilado las noticias de cada fila de datos en nuestro script de MQL5, nos encontramos con algunas filas en la columna de noticias con el nombre «(null)», lo que indica que no había noticias en ese momento, por lo que tenemos que filtrar estas filas.

news_df = df[df['Name'] != "(null)"].copy()

Al igual que estructuramos los datos anteriores para este modelo en dos columnas — ds y y —, debemos asegurarnos de que el conjunto de datos de días festivos también tenga dos columnas: ds y holiday. La columna «holiday» sirve para guardar el nombre del evento o festivo.

holidays = news_df[['Time', 'Name']].rename(columns={
    'Time': 'ds',
    'Name': 'holiday'
})

holidays['ds'] = pd.to_datetime(holidays['ds'])  # Ensure datetime format
holidays

Resultados:

ds holiday
0 2023-01-02 01:00:00 New Year's Day
1 2023-01-02 02:00:00 New Year's Day
2 2023-01-02 03:00:00 New Year's Day
3 2023-01-02 04:00:00 New Year's Day
4 2023-01-02 05:00:00 New Year's Day


Además de estas características, el dataframe «holidays» puede incluir dos columnas opcionales (lower_window y upper_window). Estas columnas indican al modelo el impacto de cada festivo/evento, tanto antes como después de que se produzca.

Sabemos que cada festividad en el mundo real no tiene su impacto únicamente en la fecha concreta en la que se celebra, sino que a menudo tiene repercusiones tanto antes como después de su celebración.

holidays['lower_window'] = 0
holidays['upper_window'] = 1  # Extend effect to 1 hour after

La columna lower_window indica en qué medida el festivo/evento afectó a los datos de la serie temporal antes de que tuviera lugar, mientras que la columna upper_window indica en qué medida el festivo/evento afectó a la serie temporal después de que tuviera lugar.

  • En el caso de la columna lower_window, sus valores pueden ser menores o iguales a cero (<=0); el valor por defecto es cero, lo que indica que el festivo/evento no afecta a ningún valor anterior de la serie temporal. Un valor de -1 indica que un determinado festivo/evento afecta al valor único anterior de la serie temporal antes de que se produjera dicho festivo/evento, etc.
  • En el caso de la columna upper_window, sus valores pueden ser mayores o iguales a cero (>=0); el valor por defecto es cero, lo que indica que el festivo/evento no afecta a ningún valor de la serie temporal tras su celebración. Un valor de 1 indica que un determinado festivo/evento afecta al siguiente valor individual de la serie temporal tras su ocurrencia, y así sucesivamente.

Ahora, añadamos estas características tal y como se ha descrito.

holidays['lower_window'] = -1 # The anticipation of the news affect the volatility 1 bar before it's release
holidays['upper_window'] = 1  # The news affects the volatility 1 bar after its release

holidays

Nuestro DataFrame de días festivos queda ahora así:

ds holiday lower_window upper_window
0 2023-01-02 01:00:00 New Year's Day -1 1
1 2023-01-02 02:00:00 New Year's Day -1 1
2 2023-01-02 03:00:00 New Year's Day -1 1
3 2023-01-02 04:00:00 New Year's Day -1 1
4 2023-01-02 05:00:00 New Year's Day -1 1
... ... ... ... ...
15369 2025-06-20 18:00:00 Eurogroup Meeting -1 1
15370 2025-06-20 19:00:00 Eurogroup Meeting -1 1
15371 2025-06-20 20:00:00 Eurogroup Meeting -1 1
15372 2025-06-20 21:00:00 Eurogroup Meeting -1 1
15373 2025-06-20 22:00:00 Eurogroup Meeting -1 1


Por último, le proporcionamos a nuestro modelo «Prophet» el DataFrame de días festivos y los datos de entrenamiento que habíamos preparado anteriormente.

model_w_holidays = Prophet(holidays=holidays)
model_w_holidays.fit(prophet_df_train)

Podemos comprobar las predicciones realizadas por el modelo entrenado con los días festivos representando gráficamente los valores predichos junto a los valores reales, tal y como hicimos anteriormente.

# Predict on training set with model
test_fcst = model_w_holidays.predict(df=prophet_df_test)

test_pred = test_fcst.yhat # We get the predictions

# Plot the forecast with the actuals

f, ax = plt.subplots(figsize=(10,5))
ax.scatter(prophet_df_test["ds"], prophet_df_test['y'], color='r')

fig = model_w_holidays.plot(test_fcst, ax=ax)

Resultados:

Figura 8

A diferencia de las predicciones realizadas por el modelo sin datos de noticias (días festivos) que se muestran en la Figura 5, que parecen estáticas, las predicciones realizadas por este nuevo modelo con datos de noticias (días festivos) parecen reflejar algunas de las fluctuaciones que el modelo anterior no tenía en cuenta.

Una vez más, evaluamos el modelo utilizando los mismos indicadores que empleamos para el modelo anterior.

results = forecast_accuracy(test_pred, prophet_df_test["y"])

for metric_name, value in results.items():
    print(f"{metric_name:<10}: {value:.6f}")

Resultados:

mape      : 0.549152
me        : -0.000633
mae       : 0.000970
mpe       : -0.175082
rmse      : 0.001487
minmax    : 0.461444
r2_score  : -2.793478

La métrica MAPE muestra que se ha producido una mejora de aproximadamente el 10 % en las predicciones realizadas por el modelo. El modelo anterior presentaba un porcentaje de errores de aproximadamente el 60 %, mientras que este presenta un porcentaje de errores de alrededor del 55 %. Esta mejora también se aprecia en el r2_score.

El modelo que comete un 55 % de errores sigue sin ser bueno; un modelo ideal tiene que cometer, como mínimo, menos del 50 % de errores (< 50 %). Aún podemos hacer algo con respecto a los días festivos (noticias) para mejorar este modelo.

En este ejemplo, hemos establecido los valores de lower_window y upper_window en -1 y 1, respectivamente, lo que significa que las noticias afectan a la volatilidad del mercado una barra antes y una barra después de su publicación. Aunque esto ha mejorado el modelo, dudo que sea la solución ideal.

Sabemos que las distintas noticias pueden tener horizontes de impacto y niveles de intensidad diferentes, por lo que aplicar estos valores fijos a todas las noticias es un error fundamental. Además, hemos tenido en cuenta todas las noticias, incluso aquellas de menor importancia, que a menudo ignoramos como operadores porque se producen con mucha frecuencia y resulta difícil medir y observar su impacto en el gráfico.

Para solucionar estos dos problemas, hay que establecer los valores de lower_window y upper_window de forma dinámica, en función del tipo de noticia y de su impacto observable históricamente

Ejemplo de pseudocódigo:

def get_windows(name):
    if "CPI" in name:
        return (-1, 4) # CPI news affects one previous bar volatility, and it affects the volatility of four bars ahead (4 hours impact forward)
    elif "NFP" in name:
        return (-1, 2) # NFP news affects one previous bar volatility, and it affects the volatility of two bars ahead (2 hours impact afterward)
    elif "FOMC" in name or "Rate" in name:
        return (-2, 6) # NFP news affects two previous bar volatility, and it affects the volatility of six bars ahead (6 hours impact afterward)
    else:
        return (0, 1)  # Default


holidays[['lower_window', 'upper_window']] = holidays['holiday'].apply(
    lambda name: pd.Series(get_windows(name))
)

Dado que existen decenas de miles de tipos de noticias distintos y hay que asegurarse de los valores de impacto aplicados, este enfoque resulta muy difícil de poner en práctica, pero es la forma ideal. Así que, haz los deberes :).

Por ahora, lo más lógico que podemos hacer es filtrar algunas noticias para quedarnos solo con aquellas de mayor y moderada importancia.

news_df = df[
    (df['Name'] != "(null)") & # Filter rows without news at all
    ((df['Importance'] == "CALENDAR_IMPORTANCE_HIGH") | (df['Importance'] == "CALENDAR_IMPORTANCE_MODERATE")) # Filter other news except high importance news
].copy()

news_df

Resultados:

Time Open High Low Close Name Sector Importance Actual Forecast Previous
7 2023.01.02 08:00:00 1.06921 1.06973 1.06724 1.06858 S&P Global Manufacturing PMI CALENDAR_SECTOR_BUSINESS CALENDAR_IMPORTANCE_MODERATE 47.10 47.400 47.400
8 2023.01.02 09:00:00 1.06878 1.06909 1.06627 1.06784 S&P Global Manufacturing PMI CALENDAR_SECTOR_BUSINESS CALENDAR_IMPORTANCE_MODERATE 47.80 47.800 47.800
31 2023.01.03 08:00:00 1.06636 1.06677 1.06514 1.06524 Unemployment CALENDAR_SECTOR_JOBS CALENDAR_IMPORTANCE_MODERATE 2.52 2.522 2.538
37 2023.01.03 14:00:00 1.05283 1.05490 1.05241 1.05355 S&P Global Manufacturing PMI CALENDAR_SECTOR_BUSINESS CALENDAR_IMPORTANCE_HIGH 46.20 46.200 46.200
38 2023.01.03 15:00:00 1.05353 1.05698 1.05304 1.05602 Construction Spending m/m CALENDAR_SECTOR_HOUSING CALENDAR_IMPORTANCE_MODERATE 0.20 0.200 -0.300


Queda genial: tras extraer las columnas de hora y nombre al DataFrame «holidays», añadimos los valores lower_window y upper_window.

holidays = news_df[['Time', 'Name']].rename(columns={
    'Time': 'ds',
    'Name': 'holiday'
})

holidays['ds'] = pd.to_datetime(holidays['ds'])  # Ensure datetime format

holidays['lower_window'] = 0
holidays['upper_window'] = 1 

holidays

Tras entrenar el modelo, a continuación se muestra un gráfico en el que aparecen los valores reales de las muestras de entrenamiento y de prueba, en negro y rojo respectivamente, y las predicciones de la muestra de prueba, en azul.

Figura 9

El modelo volvió a mejorar, con un porcentaje de errores de aproximadamente el 50 % según la métrica MAPE. Ahora podemos utilizar este modelo de regresión para realizar predicciones.

mape      : 0.506827
me        : -0.000053
mae       : 0.000783
mpe       : 0.271597
rmse      : 0.001234
minmax    : 0.320422
r2_score  : -3.318859

Quizá hayas observado que hemos importado las noticias por separado desde un archivo CSV, mientras que las hemos utilizado junto con los datos de entrenamiento importados directamente desde MetaTrader 5. 

El modelo Prophet sincroniza las fechas del DataFrame «holidays» con las del conjunto de datos de entrenamiento principal, siempre y cuando las marcas de tiempo del DataFrame «holidays» se encuentren dentro del periodo de entrenamiento o de predicción futura. 

Aunque este modelo permite sincronizar las fechas, debes asegurarte expresamente de que ambos conjuntos de datos tengan las mismas fechas de inicio para sacar el máximo partido a ambos.

He tenido que volver atrás y modificar el proceso de obtención de información sobre precios desde MetaTrader 5 dentro de main.ipynb; ahora las fechas de inicio y fin coinciden con las utilizadas en el archivo de script OHLC + News.mq5.

# set time zone to UTC
timezone = pytz.timezone("Etc/UTC")

# create 'datetime' objects in UTC-time to avoid the implementation of a local time zone offset
utc_from = datetime(2023, 1, 1, tzinfo=timezone)
utc_to = datetime(2025, 6, 24, hour = 0, tzinfo=timezone)

rates = mt5.copy_rates_range(symbol, timeframe, utc_from, utc_to)


Creación del robot de trading para MetaTrader 5 basado en el modelo «Prophet» 

Para crear un robot de trading basado en el modelo Prophet, primero tenemos que ser capaces de utilizarlo para realizar predicciones en tiempo real sobre la variable objetivo (en este caso, la volatilidad).

Para lograrlo, necesitamos un pipeline que nos permita obtener la información más reciente del mercado (símbolos), incluidas las últimas noticias, todo de una sola vez. En el script de entrenamiento main.ipynb, hemos recopilado datos de MetaTrader 5 utilizando el paquete MetaTrader 5-Python; sin embargo, este paquete no ofrece ninguna forma de obtener noticias, por lo que, sin duda, tendremos que utilizar MQL5 para este proceso.

La idea es intercambiar datos entre el script de Python (robot de trading) y un Asesor Experto (EA) en MQL5.

  • Un EA (Data for Prophet.mq5) asociado al gráfico de MetaTrader 5 guarda periódicamente los datos (noticias y valores OHLC) de MetaTrader 5 en un archivo CSV ubicado en la carpeta común.
  • A continuación, el script de Python (Prophet-trading-bot.py) lee este archivo para entrenar periódicamente el modelo Prophet.
  • Una vez entrenado, el modelo se utiliza para realizar predicciones que, a su vez, sirven para tomar decisiones de trading dentro del mismo script de Python.

Nombre del archivo: Data for Prophet.mq5

input uint collect_news_interval_seconds = 60;
input uint training_bars = 1000;

input ENUM_TIMEFRAMES timeframe = PERIOD_H1;
//... other lines of code
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {  
//--- create timer

   EventSetTimer(collect_news_interval_seconds);
   
   if (!ChartSetSymbolPeriod(0, Symbol(), timeframe))
      return INIT_FAILED;
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---   
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   MqlDateTime time_struct;
   TimeToStruct(TimeGMT(), time_struct);
   
   SaveNews(StringFormat("%s.%s.OHLC.date=%s.hour=%d + News.csv",Symbol(),EnumToString(timeframe), TimeToString(TimeGMT(), TIME_DATE), time_struct.hour));
  }

Para asegurarnos de que estamos trabajando con el archivo correcto, se utilizan la fecha y la hora actual (en hora UTC) para nombrar el archivo CSV.

Este asesor experto recopila noticias y otros valores y los guarda en un archivo CSV cada minuto de forma predeterminada, según la función OnTimer.

Dentro de un script de Python, cargamos el archivo CSV desde la carpeta común de la misma forma e importamos los datos.

Nombre del archivo: Prophet-trading-bot.py

def prophet_vol_predict() -> float:

    # Getting the data with news
    
    now_utc = datetime.utcnow()
    current_date = now_utc.strftime("%Y.%m.%d")
    current_hour = now_utc.hour

    filename = f"{symbol}.{timeframe}.OHLC.date={current_date}.hour={current_hour} + News.csv" # the same file naming as in MQL5 script
    common_path = os.path.join(terminal.common_data_path(), "Files")
    csv_path = os.path.join(common_path, filename)
    
    # Keep trying to read a CSV file until it is found, as there could be a temporary difference in values for the file due to the change in time
    while True:
        if os.path.exists(csv_path):
            try:
                rates_df = pd.read_csv(csv_path)
                rates_df["Time"] = pd.to_datetime(rates_df["Time"], unit="s", errors="ignore")  # Convert time from seconds to datetime
                
                print("File loaded successfully.")
                break  # Exit the loop once file is read
            except Exception as e:
                print(f"Error reading the file: {e}")
                time.sleep(30)
        else:
            print("File not found. Retrying in 30 seconds...")
            time.sleep(30)
 

Preparamos la columna de volatilidad y extraemos los nombres de las noticias para los datos de entrenamiento y los datos de días festivos, respectivamente.

# Getting continous variables for the prophet model
    
prophet_df = pd.DataFrame({
    "time": rates_df["Time"],
    "volatility": rates_df["High"] - rates_df["Low"]
}).set_index("time")

prophet_df = prophet_df.reset_index().rename(columns={"time": "ds", "volatility": "y"}).copy()
    
print("Prophet df\n",prophet_df.head())
    
# Getting the news data for the model as well
    
news_df = rates_df[
    (rates_df['Name'] != "(null)") & # Filter rows without news at all
    ((rates_df['Importance'] == "CALENDAR_IMPORTANCE_HIGH") | (rates_df['Importance'] == "CALENDAR_IMPORTANCE_MODERATE")) # Filter other news except high importance news
].copy()

holidays = news_df[['Time', 'Name']].rename(columns={
    'Time': 'ds',
    'Name': 'holiday'
})

holidays['ds'] = pd.to_datetime(holidays['ds'])  # Ensure datetime format

holidays['lower_window'] = 0
holidays['upper_window'] = 1 

print("Holidays df\n", holidays)

Al final de la función prophet_vol_pred, entrenamos el modelo con la información recibida y devolvemos un único valor previsto, que representa la volatilidad prevista que, según el modelo, se producirá en la próxima barra del mercado.

    # re-training the prophet model
    
    prophet_model = Prophet(holidays=holidays)
    prophet_model.fit(prophet_df)
    
    # Making future predictions
    
    future = prophet_model.make_future_dataframe(periods=1) # prepare the dataframe for a single value prediction
    forecast = prophet_model.predict(future) # Predict the next one value
    
    return forecast.yhat[0] # return a single predicted value

Al igual que ocurre con otros modelos de aprendizaje automático utilizados en la previsión de series temporales, debemos actualizarlos con mucha frecuencia para garantizar que dispongan de la información más reciente que sea relevante para las previsiones futuras (predicciones). Esta es la razón principal por la que volvemos a entrenar el modelo antes de realizar nuevas predicciones.

Ejecutemos la función y veamos el resultado.

print("predicted volatility: ",prophet_vol_predict())

Resultados:

El archivo se ha cargado correctamente.
Prophet df
                     ds        y
0  2025.04.29 01:00:00  0.00100 
1  2025.04.29 02:00:00  0.00210 
2  2025.04.29 03:00:00  0.00170 
3  2025.04.29 04:00:00  0.00215 
4  2025.04.29 05:00:00  0.00278 
Holidays df
                      ds                                     holiday  lower_window  upper_window
8   2025-04-29 09:00:00                        GfK Consumer Climate             0             1 
14  2025-04-29 15:00:00          Retail Inventories excl. Autos m/m             0             1 
31  2025-04-30 08:00:00                       Consumer Spending m/m             0             1 
33  2025-04-30 10:00:00                                Unemployment             0             1 
35  2025-04-30 12:00:00                                     GDP y/y             0             1 
..                  ...                                         ...           ...           ... 
978 2025-06-24 19:00:00                 FOMC Member Williams Speech             0             1
979 2025-06-24 20:00:00                         2-Year Note Auction             0             1
982 2025-06-24 23:00:00  Fed Vice Chair for Supervision Barr Speech             0             1
984 2025-06-25 01:00:00                            Jobseekers Total             0             1
994 2025-06-25 11:00:00  Bbk Executive Board Member Mauderer Speech             0             1

[186 rows x 4 columns]
16:01:50 - cmdstanpy - INFO - Chain [1] start processing
16:01:50 - cmdstanpy - INFO - Chain [1] done processing
predicted volatility:  0.0013592111956094713

Ahora que somos capaces de obtener el valor previsto, podemos utilizarlo en nuestra estrategia de trading.

symbol = "EURUSD"
timeframe = "PERIOD_H1"
terminal = CTerminalInfo()
m_position = CPositionInfo()

def main():
    
    m_symbol = CSymbolInfo(symbol=symbol)
    
    magic_number = 25062025
    slippage = 100
    
    m_trade = CTrade(magic_number=magic_number,
                     filling_type_symbol=symbol,
                     deviation_points=slippage)
    
    m_symbol.refresh_rates() # Get recent information from the market
    
    # we want to open random buy and sell trades if they don't exist and use the predicted volatility to set our stoploss and takeprofit targets
    
    predicted_volatility = prophet_vol_predict()
    print("predicted volatility: ",prophet_vol_predict())
    
    if pos_exists(mt5.POSITION_TYPE_BUY, magic_number, symbol) is False:
        m_trade.buy(volume=m_symbol.lots_min(), 
                    symbol=symbol,
                    price=m_symbol.ask(),
                    sl=m_symbol.ask()-predicted_volatility,
                    tp=m_symbol.ask()+predicted_volatility)
        
    if pos_exists(mt5.POSITION_TYPE_SELL, magic_number, symbol) is False:
        m_trade.sell(volume=m_symbol.lots_min(), 
                     symbol=symbol,
                     price=m_symbol.bid(),
                     sl=m_symbol.bid()+predicted_volatility,
                     tp=m_symbol.bid()-predicted_volatility)

La función anterior obtiene la volatilidad prevista del modelo Prophet y la utiliza para establecer los objetivos de stop-loss y take-profit en nuestras operaciones. Antes de abrir una operación aleatoria, comprueba si ya existe una posición (operación) del mismo tipo antes de abrirla.

Llamada a una función:

main()

Resultado:

 

Figura 10

Se abrieron dos operaciones opuestas en MetaTrader 5 con valores de stop loss y take profit que corresponden a la volatilidad prevista por el modelo.

Podemos automatizar este proceso de formación y supervisar periódicamente las operaciones de trading y las señales.

schedule.every(1).minute.do(main) # train and run trading operations after every one minute

while True:
    
    schedule.run_pending()
    time.sleep(1)



Conclusión

Aunque algunos artículos, publicaciones y tutoriales en Internet afirman que el modelo Prophet es adecuado para la predicción de series temporales, creo que es uno de los peores modelos que hemos analizado en esta serie de artículos.

Puede que resulte útil para predecir algunos problemas sencillos de series temporales, como la demanda de una empresa que dependa de las condiciones meteorológicas, los días festivos o algún tipo de patrón estacional; sin embargo, los mercados financieros son mucho más complejos que eso, tal y como se aprecia en las figuras (05, 06, 07, 08, 09), que muestran los valores reales y los valores previstos en las muestras de prueba. El modelo «Prophet» no consigue que la mayoría de las predicciones se acerquen a los valores reales.

Entiendo que hay ciertas cosas que se pueden hacer para mejorarlo, pero yo recomendaría utilizarlo, por ahora, en problemas sencillos.

Entre las limitaciones de este modelo cabe destacar las siguientes:

  • Estructura de modelo sencilla que no admite interacciones complejas.
  • No se adapta bien a la volatilidad; como se ha visto anteriormente, no da buenos resultados con los datos del mercado de divisas.
  • Sin modelos multivariantes: admite dos características: el tiempo y la variable de respuesta.
  • No ofrece validación cruzada ni facilidades claras para el ajuste de hiperparámetros, ya que debes controlar tú mismo la tendencia, la estacionalidad y los puntos de cambio.

Saludos cordiales.


Fuentes y referencias


Tabla de archivos adjuntos

Nombre del archivo Descripción y uso
Python code\main.ipynb Un cuaderno Jupyter para el análisis de datos y la exploración del modelo Prophet.
Python code\Prophet-trading-bot.py Robot de trading MetaTrader 5 basado en Python.
Python code\requirementx.txt Un archivo de texto que contiene las dependencias de Python y su número de versión.
Python code\error_description.py Contiene la descripción de todos los códigos de error generados por MetaTrader 5.
Python code\Trade\* Contiene las clases Trade (CTrade, CPositionInfo, etc.) para Python, similares a las disponibles en el lenguaje MQL5.
Experts\Data for Prophet.mq5 Un asesor experto que recopila y almacena periódicamente los datos para entrenar el modelo Prophet en un archivo CSV.
Scripts\OHLC + News.mq5  Un script para recopilar y almacenar en un archivo CSV los datos para entrenar el modelo Prophet. 

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

Archivos adjuntos |
Attachments.zip (803.62 KB)
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 29): Boom and Crash Interceptor EA Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 29): Boom and Crash Interceptor EA
Descubre cómo el EA «Boom & Crash Interceptor» transforma tus gráficos en un sistema de alertas proactivo, capaz de detectar movimientos explosivos mediante análisis ultrarrápidos, detección de picos de volatilidad, confirmación de tendencias y filtros de zonas de pivote. Con las flechas verdes «Boom» y rojas «Crash», que te guían en cada decisión, esta herramienta elimina el ruido y te permite sacar partido de los picos del mercado como nunca antes. Descúbrelo y comprueba cómo funciona y por qué puede convertirse en tu próxima ventaja imprescindible.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Dominando los registros (Parte 9): Implementación del patrón Builder y adición de configuraciones predeterminadas Dominando los registros (Parte 9): Implementación del patrón Builder y adición de configuraciones predeterminadas
Este artículo muestra cómo simplificar drásticamente el uso de la biblioteca Logify con el patrón Builder y las configuraciones predeterminadas automáticas. Explica la estructura de los constructores especializados, cómo utilizarlos con la función de autocompletado inteligente y cómo garantizar un registro funcional incluso sin configuración manual. También incluye ajustes para la versión 5100 de MetaTrader 5.