Obtenga una ventaja sobre cualquier mercado (Parte II): Predicción de indicadores técnicos
Introducción
Los inversores que tratan de aplicar el aprendizaje automático en entornos de negociación electrónica se enfrentan a numerosos retos, y la realidad es que muchos no logran los resultados deseados. Este artículo pretende destacar algunas razones por las que, en mi opinión, un aspirante a operador algorítmico puede no lograr rendimientos satisfactorios en relación con la complejidad de sus estrategias. Demostraré por qué predecir el precio de un valor financiero a menudo tiene dificultades para superar el 50% de precisión y cómo, en cambio, centrarse en predecir los valores de los indicadores técnicos puede mejorar la precisión hasta alrededor del 70%. Esta guía le proporcionará instrucciones paso a paso sobre las mejores prácticas para el análisis de series temporales.
Al final de este artículo, tendrá una sólida comprensión de cómo mejorar la precisión de sus modelos de aprendizaje automático y descubrir indicadores adelantados de los cambios del mercado con más eficacia que otros participantes utilizando Python y MQL5.
Previsión de los valores de los indicadores
Obtendremos datos históricos de nuestro terminal MetaTrader 5 y los analizaremos utilizando las bibliotecas estándar de Python. Este análisis mostrará que pronosticar cambios en los valores de los indicadores es más efectivo que predecir cambios en los precios de los valores. Esto es cierto porque sólo podemos observar parcialmente los factores que influyen en el precio de un valor. En realidad, no podemos modelar cada una de las variables que afectan el precio de un símbolo debido a su gran cantidad y complejidad. Sin embargo, podemos observar plenamente todos los factores que afectan el valor de un indicador técnico.
Primero, demostraré el principio y luego explicaré por qué este enfoque funciona mejor al final de nuestra discusión. Al ver primero el principio en acción, la explicación teórica será más fácil de entender. Comencemos seleccionando el ícono de lista de símbolos en el menú justo encima del gráfico.
Nuestros objetivos aquí se centran en la obtención de datos:
- Abra su terminal MetaTrader 5.
- Seleccione el icono de lista de símbolos en el menú sobre el gráfico.
- Elija el símbolo y el período de tiempo deseado para su análisis.
- Exporte los datos históricos a un archivo de valores separados por comas (csv).
Fig. 1: Obtención de datos históricos.
Busca el símbolo que deseas modelar.
Fig. 2: Buscando el símbolo deseado.
Luego, seleccione el mosaico “barras” en el menú y asegúrese de solicitar la mayor cantidad de datos posible.
Fig. 3: Solicitud de datos históricos.
Seleccione barras de exportación en el menú inferior para que podamos comenzar a analizar nuestros datos en Python.
,
Fig. 4: Exportando nuestros datos históricos.
Como de costumbre, comenzamos importando las bibliotecas que necesitaremos.
#Load libraries import pandas as pd import numpy as np import matplotlib.pyplot as plt
A continuación, leemos nuestros datos históricos del mercado. Tenga en cuenta que el terminal MetaTrader 5 exporta archivos .csv que están delimitados por tabulaciones, por lo tanto, pasamos la notación de tabulación al parámetro separador de nuestra llamada a pandas read csv.
#Read the data csv = pd.read_csv("/content/Volatility 75 Index_M1_20190101_20240131.csv",sep="\t") csv
Después de leer nuestros datos históricos, se verá así. Necesitamos reformatear un poco los títulos de las columnas y también agregar un indicador técnico.
Fig 5: Nuestros datos históricos de nuestro terminal MetaTrader 5.
Ahora cambiaremos el nombre de las columnas.
#Format the data csv.rename(columns={"<DATE>":"date","<TIME>":"time","<TICKVOL>":"tickvol","<VOL>":"vol","<SPREAD>":"spread","<OPEN>":"open","<HIGH>":"high","<LOW>":"low","<CLOSE>":"close"},inplace=True) csv.ta.sma(length= 60,append=True) csv.dropna(inplace=True) csv
Fig 6: Formateando nuestros datos.
Ahora podemos definir nuestras entradas.
#Define the inputs predictors = ["open","high","low","close","SMA_60"]
A continuación, escalaremos nuestros datos para que nuestro modelo pueda entrenarse lo suficiente.
#Scale the data
csv["open"] = csv["open"] /csv.loc[0,"open"]
csv["high"] = csv["high"] /csv.loc[0,"high"]
csv["low"] = csv["low"] /csv.loc[0,"low"]
csv["close"] = csv["close"] /csv.loc[0,"close"]
csv["SMA_60"] = csv["SMA_60"] /csv.loc[0,"SMA_60"]
Abordaremos esta tarea como un problema de clasificación. Nuestro objetivo será categórico. Un valor objetivo de 1 significa que el precio del valor se apreció durante 60 velas, y un valor objetivo de 0 significa que el precio se depreció durante el mismo horizonte. Observe que tenemos dos objetivos. Un objetivo es monitorear el cambio en el precio de cierre, mientras que el otro es monitorear el cambio en el promedio móvil.
Utilizaremos el mismo patrón de codificación en los cambios en el promedio móvil, un valor objetivo de 1 significa que el valor del promedio móvil futuro en las próximas 60 velas será mayor y, por el contrario, un valor objetivo de 0 significa que el valor del promedio móvil caerá en las próximas 60 velas.
#Define the close csv["Target Close"] = 0 csv["Target MA"] = 0
Define hasta qué punto en el futuro te gustaría hacer el pronóstico.
#Define the forecast horizon look_ahead = 60
Codifique los valores objetivo.
#Set the targets
csv.loc[csv["close"] > csv["close"].shift(-look_ahead) ,"Target Close"] = 0
csv.loc[csv["close"] < csv["close"].shift(-look_ahead) ,"Target Close"] = 1
csv.loc[csv["SMA_60"] > csv["SMA_60"].shift(-look_ahead) ,"Target MA"] = 0
csv.loc[csv["SMA_60"] < csv["SMA_60"].shift(-look_ahead) ,"Target MA"] = 1
csv = csv[:-look_ahead]
Ajustaremos el mismo grupo de modelos en el mismo conjunto de datos, recuerde que la única diferencia es que la primera vez nuestros modelos intentarán predecir el cambio en el precio de cierre mientras que en la segunda prueba intentarán predecir el cambio en un indicador técnico, en nuestro ejemplo el promedio móvil.
Después de definir nuestros objetivos, podemos avanzar hacia la importación de los modelos que necesitamos para nuestro análisis.
#Get ready from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.linear_model import LogisticRegression from xgboost import XGBClassifier from sklearn.neural_network import MLPClassifier from sklearn.metrics import accuracy_score from sklearn.decomposition import PCA from sklearn.model_selection import TimeSeriesSplit
Prepararemos una serie de tiempo dividida para evaluar dónde nuestro error de validación es menor. Además, transformaremos nuestros datos de entrada utilizando las funciones de análisis de componentes principales (PCA, Principal Components Analysis) en sklearn. Este paso es necesario porque nuestras columnas de entrada pueden estar correlacionadas, lo que podría dificultar el proceso de aprendizaje de nuestro modelo. Al realizar PCA, transformamos nuestro conjunto de datos en una forma que garantiza que no haya correlación entre las entradas, mejorando así el rendimiento de nuestro modelo.
#Time series split splits = 10 gap = look_ahead models_close = ["Logistic Regression","LDA","XGB","Nerual Net Simple","Nerual Net Large"] models_ma = ["Logistic Regression","LDA","XGB","Nerual Net Simple","Nerual Net Large"] #Prepare the data pca = PCA() csv_reduced = pd.DataFrame(pca.fit_transform(csv.loc[:,predictors]))
Observemos ahora nuestros niveles de precisión, utilizando una red neuronal que intenta pronosticar cambios en el precio de cierre directamente.
#Fit the neural network predicting close price model_close = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1) model_close.fit(csv_reduced.loc[0:300000,:],csv.loc[0:300000,"Target Close"]) print("Close accuracy: ",accuracy_score(csv.loc[300070:,"Target Close"], model_close.predict(csv_reduced.loc[300070:,:])))
Nuestra precisión al pronosticar cambios en el precio de cierre fue del 49,9%. Esto no es impresionante considerando la cantidad de complejidad que hemos aceptado, podríamos haber obtenido el mismo nivel de precisión con un modelo más simple que sea más fácil de mantener y entender, además, si solo tenemos razón el 49% del tiempo, permaneceremos en una posición no rentable. Comparemos esto con nuestra precisión al pronosticar cambios en el indicador de promedio móvil.
#Fit the model predicting the moving average model_ma = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1) model_ma.fit(csv_reduced.loc[0:300000,:],csv.loc[0:300000,"Target MA"]) print("MA accuracy: ",accuracy_score(csv.loc[300070:,"Target MA"], model_ma.predict(csv_reduced.loc[300070:,:])))
La precisión de nuestro modelo fue del 68,8% al pronosticar los cambios en la media móvil, frente al 49,9% al pronosticar los cambios en el precio. Este es un nivel aceptable de precisión en relación con la complejidad de la técnica de modelado que estamos utilizando.
Ahora ajustaremos una variedad de modelos y veremos cuál modelo puede predecir mejor los cambios en el precio y cuál modelo puede predecir mejor los cambios en el promedio móvil.
#Error metrics tscv = TimeSeriesSplit(n_splits=splits,gap=gap) error_close_df = pd.DataFrame(index=np.arange(0,splits),columns=models_close) error_ma_df = pd.DataFrame(index=np.arange(0,splits),columns=models_ma)
Primero evaluaremos la precisión de cada uno de nuestros modelos seleccionados tratando de pronosticar el precio de cierre.
#Training each model to predict changes in the close price for i,(train,test) in enumerate(tscv.split(csv)): model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1) model.fit(csv_reduced.loc[train[0]:train[-1],:],csv.loc[train[0]:train[-1],"Target Close"]) error_close_df.iloc[i,4] = accuracy_score(csv.loc[test[0]:test[-1],"Target Close"],model.predict(csv_reduced.loc[test[0]:test[-1],:]))
Fig. 7: Resultados de precisión de diferentes modelos que intentan clasificar los cambios en el precio.
Fig. 8: Una visualización del rendimiento de cada uno de nuestros modelos.
Podemos evaluar la máxima precisión registrada por cada modelo al pronosticar el precio de cierre.
for i in enumerate(np.arange(0,error_close_df.shape[1])): print(error_close_df.columns[i[0]]," ", error_close_df.iloc[:,i[0]].max())
LDA 0.5192457894678943
XGB 0.5119523008041539
Neural Net Simple 0.5234700724948571
Neural Net Large 0.5186627504042771
Como podemos ver, ninguno de nuestros modelos funcionó excepcionalmente bien. Todos estaban dentro de una banda del 50%, sin embargo, en nuestro modelo de Análisis Discriminante Lineal (LDA) el que tuvo el mejor desempeño fue el del grupo.
Por otra parte, ahora hemos establecido que nuestros modelos exhibirán mayor precisión al pronosticar cambios en ciertos indicadores técnicos. Ahora queremos determinar, de nuestro grupo de candidatos, qué modelo funciona mejor al pronosticar cambios en el promedio móvil.
#Training each model to predict changes in a technical indicator (in this example simple moving average) instead of close price. for i,(train,test) in enumerate(tscv.split(csv)): model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1) model.fit(csv_reduced.loc[train[0]:train[-1],:],csv.loc[train[0]:train[-1],"Target MA"]) error_ma_df.iloc[i,4] = accuracy_score(csv.loc[test[0]:test[-1],"Target MA"],model.predict(csv_reduced.loc[test[0]:test[-1],:]))
Fig. 9: La precisión de nuestros modelos al intentar predecir cambios en la media móvil.
Fig. 10: Una visualización de la precisión de nuestro modelo al pronosticar cambios en el promedio móvil.
Evaluaremos la máxima precisión registrada por cada tipo de modelo.
for i in enumerate(np.arrange(0,error_ma_df.shape[1])): print(error_ma_df.columns[i[0]]," ", error_ma_df.iloc[:,i[0]].max())
Logistic Regression 0.6927054112625546
LDA 0.696401658911147
XGB 0.6932664488520731
Neural Net Simple 0.6947955513019373
Neural Net Large 0.6965006655445914
Tenga en cuenta que, si bien la red neuronal grande alcanzó el nivel de precisión más alto, no deseamos emplearla en producción porque su rendimiento era inestable. Podemos observar esto desde los 2 puntos en el gráfico del rendimiento de la red neuronal grande que están muy por debajo de su rendimiento promedio. Por lo tanto, podemos observar a partir de los resultados que, dado nuestro conjunto de datos actual, el modelo ideal debería ser más complejo que una simple regresión logística y menos complicado que una gran red neuronal.
Continuaremos desarrollando una estrategia comercial que pronostique movimientos futuros en el indicador de media móvil como señal comercial. Nuestro modelo de elección será la red neuronal pequeña porque parece mucho más estable.
Primero importamos las bibliotecas que necesitamos.
#Import the libraries we need import MetaTrader5 as mt5 import pandas_ta as ta import pandas as pd
A continuación, configuramos nuestro entorno comercial.
#Trading global variables MARKET_SYMBOL = 'Volatility 75 Index' #This data frame will store the most recent price update last_close = pd.DataFrame() #We may not always enter at the price we want, how much deviation can we tolerate? DEVIATION = 10000 #We will always enter at the minimum volume VOLUME = 0 #How many times the minimum volume should our positions be LOT_MUTLIPLE = 1 #What timeframe are we working on? TIMEFRAME = mt5.TIMEFRAME_M1 #Which model have we decided to work with? neural_network_model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1)
Determinemos el volumen mínimo permitido en el símbolo que deseamos operar.
#Determine the minimum volume for index,symbol in enumerate(symbols): if symbol.name == MARKET_SYMBOL: print(f"{symbol.name} has minimum volume: {symbol.volume_min}") VOLUME = symbol.volume_min * LOT_MULTIPLE
Ahora podemos crear una función que entregará nuestras órdenes de mercado por nosotros.
# function to send a market order def market_order(symbol, volume, order_type, **kwargs): #Fetching the current bid and ask prices tick = mt5.symbol_info_tick(symbol) #Creating a dictionary to keep track of order direction order_dict = {'buy': 0, 'sell': 1} price_dict = {'buy': tick.ask, 'sell': tick.bid} request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": order_dict[order_type], "price": price_dict[order_type], "deviation": DEVIATION, "magic": 100, "comment": "Indicator Forecast Market Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result
Además, también necesitamos otra función que nos ayude a cerrar nuestras órdenes de mercado.
# Closing our order based on ticket id def close_order(ticket): positions = mt5.positions_get() for pos in positions: tick = mt5.symbol_info_tick(pos.symbol) #validating that the order is for this symbol type_dict = {0: 1, 1: 0} # 0 represents buy, 1 represents sell - inverting order_type to close the position price_dict = {0: tick.ask, 1: tick.bid} #bid ask prices if pos.ticket == ticket: request = { "action": mt5.TRADE_ACTION_DEAL, "position": pos.ticket, "symbol": pos.symbol, "volume": pos.volume, "type": type_dict[pos.type], "price": price_dict[pos.type], "deviation": DEVIATION, "magic": 10000, "comment": "Indicator Forecast Market Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result return 'Ticket does not exist'
Además, debemos definir el rango de fechas del que queremos solicitar datos.
#Update our date from and date to date_from = datetime(2024,1,1) date_to = datetime.now()
Antes de poder transmitir los datos solicitados al corredor, primero debemos preprocesar los datos en el mismo formato que nuestro modelo observó durante el entrenamiento.
#Let's create a function to preprocess our data def preprocess(df): #Calculating 60 period Simple Moving Average df.ta.sma(length=60,append=True) #Drop any rows that have missing values df.dropna(axis=0,inplace=True)
Continuando, debemos ser capaces de obtener un pronóstico de nuestra red neuronal e interpretar ese pronóstico como una señal comercial para tomar posiciones largas o cortas.
#Get signals from our model def ai_signal(): #Fetch OHLC data df = pd.DataFrame(mt5.copy_rates_range(market_symbol,TIMEFRAME,date_from,date_to)) #Process the data df['time'] = pd.to_datetime(df['time'],unit='s') df['target'] = (df['close'].shift(-1) > df['close']).astype(int) preprocess(df) #Select the last row last_close = df.iloc[-1:,1:] #Remove the target column last_close.pop('target') #Use the last row to generate a forecast from our moving average forecast model #Remember 1 means buy and 0 means sell forecast = neural_network_model.predict(last_close) return forecast[0]Finalmente unimos todo esto para crear nuestra estrategia comercial.
#Now we define the main body of our trading algorithm if __name__ == '__main__': #We'll use an infinite loop to keep the program running while True: #Fetching model prediction signal = ai_signal() #Decoding model prediction into an action if signal == 1: direction = 'buy' elif signal == 0: direction = 'sell' print(f'AI Forecast: {direction}') #Opening A Buy Trade #But first we need to ensure there are no opposite trades open on the same symbol if direction == 'buy': #Close any sell positions for pos in mt5.positions_get(): if pos.type == 1: #This is an open sell order, and we need to close it close_order(pos.ticket) if not mt5.positions_totoal(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) #Opening A Sell Trade elif direction == 'sell': #Close any buy positions for pos in mt5.positions_get(): if pos.type == 0: #This is an open buy order, and we need to close it close_order(pos.ticket) if not mt5.positions_get(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) print('time: ', datetime.now()) print('-------\n') time.sleep(60)
Fig. 11: Nuestro modelo en acción.
Explicación teórica
En opinión del autor, una de las razones por las que podríamos estar observando una mayor precisión al pronosticar cambios en los indicadores técnicos tendría que ser el hecho de que nunca podemos observar todas las variables que afectan el precio de un valor. En el mejor de los casos, sólo podemos observarlos parcialmente, mientras que cuando pronosticamos los cambios de un indicador técnico, somos plenamente conscientes de todas las entradas que han influido en el indicador técnico. Recordemos que incluso conocemos la fórmula precisa de cualquier indicador técnico.
Fig. 12: Conocemos la descripción matemática de todos los indicadores técnicos, pero no existe una fórmula matemática del precio de cierre.
Por ejemplo, el indicador técnico 'Índice de flujo de dinero' (MFI, Money Flow Index) se calcula utilizando la fórmula anterior. Por lo tanto, si queremos predecir cambios en el MFI, sólo necesitamos los componentes de su fórmula: los precios de cierre, mínimo y máximo.
Por el contrario, cuando pronosticamos el precio de cierre, no tenemos una fórmula específica que nos diga qué insumos lo afectan. Esto a menudo da como resultado una menor precisión, lo que sugiere que nuestro conjunto actual de entradas puede no ser informativo o que hemos introducido demasiado ruido al elegir entradas deficientes.
En términos fundamentales, el objetivo del aprendizaje automático es encontrar un objetivo determinado por un conjunto de entradas. Cuando utilizamos un indicador técnico como objetivo, estamos afirmando esencialmente que el indicador técnico está influido por los precios de apertura, cierre, mínimo y máximo, lo cual es cierto. Sin embargo, como desarrolladores de algoritmos, a menudo utilizamos nuestras herramientas al revés. Utilizamos una colección de datos de precios e indicadores técnicos para pronosticar el precio, lo que implica que los indicadores técnicos influyen en el precio, lo que no es el caso y nunca lo será.
Cuando intentamos aprender un objetivo cuya función subyacente no se conoce, caemos potencialmente víctimas de lo que se conoce como una regresión espuria, ya lo discutimos ampliamente en nuestra discusión anterior. En términos sencillos, es posible que su modelo aprenda una relación que no existe en la vida real. Además, este defecto puede enmascararse con tasas de error engañosamente bajas en la validación, haciendo que parezca que el modelo ha aprendido lo suficiente, aunque en realidad no haya aprendido nada sobre el mundo real.
Para ilustrar lo que es una regresión espuria, imagine que usted y yo bajamos por una colina y justo sobre el horizonte podemos ver una forma imprecisa. Estamos demasiado lejos para distinguir lo que es, pero basándome en lo que he visto, grito «hay un perro ahí abajo». Al llegar encontramos un arbusto, pero detrás del arbusto hay un perro.
Fig. 13: ¿Podría haber visto al perro?
¿Ya ve el problema? Obviamente, me encantaría reivindicar la victoria como testimonio de mi perfecta visión 20/20, pero usted sabe que, fundamentalmente hablando, no había forma posible de que hubiera visto al perro desde donde estábamos cuando hice la declaración, simplemente estábamos demasiado lejos y la figura era demasiado imprecisa desde donde estábamos.
Simplemente no había relación entre las entradas que vi en la cima de la montaña y la conclusión a la que llegué. Es decir, los datos de entrada y los de salida eran independientes entre sí. Siempre que un modelo considera datos de entrada que no tienen relación con los datos de salida pero consigue producir la respuesta correcta, lo llamamos una regresión espuria. ¡Las regresiones espurias ocurren todo el tiempo!
Dado que no disponemos de fórmulas técnicas que describan lo que afecta al precio de un valor, somos propensos a realizar regresiones espurias utilizando entradas que no influyen en nuestro objetivo, que es el precio de cierre. Intentar demostrar que una regresión no es espuria puede ser todo un reto, es más fácil utilizar un objetivo que tenga una relación conocida con las entradas.
Conclusión
Este artículo ha demostrado por qué la práctica de pronosticar directamente el precio de cierre debería ser potencialmente desaprobada en favor de pronosticar en su lugar los cambios en los indicadores técnicos. Es necesario seguir investigando para averiguar si existen otros indicadores técnicos que podamos pronosticar con más precisión que la media móvil. Sin embargo, también hay que advertir a los lectores de que, aunque nuestra precisión en la previsión de la media móvil es relativamente alta, sigue existiendo un desfase entre los cambios de la media móvil y los cambios en el precio.
En otras palabras, es posible que la media móvil esté cayendo mientras el precio está subiendo, sin embargo, si nosotros como comunidad MQL5 trabajamos colectivamente para mejorar este algoritmo, entonces estoy seguro de que eventualmente podremos alcanzar nuevos niveles de precisión.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/14936
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso