Predicción del precio de acciones con machine learning en Python

0
232
Precio pronosticado por el modelo

Este artículo es una guía básica que lleva al lector a través de una demostración de cómo podemos hacer una predicción de precios de acciones utilizando LSTM en Python.

 Cómo predecir precios de acciones utilizando LSTM y Python

 La suposición básica de cualquier modelo basado en Aprendizaje Automático (ML) tradicional es que todas las observaciones deben ser independientes entre sí, lo que significa que no debería haber ninguna asociación entre cada registro/fila de datos. Sin embargo, en el caso de cualquier dato de series temporales, cada observación depende de observaciones pasadas. Por ejemplo, el precio de las acciones en el mercado depende de los precios de los días pasados que generalmente definen su tendencia, o al menos esa es la teoría en el análisis técnico. Sin embargo, también hay muchos otros factores que definen los precios del mercado.

Necesitamos modelado de series temporales (comúnmente conocido como pronóstico) para modelar datos dependientes del tiempo de esta manera. En este artículo, nos centraremos en una de las técnicas de modelado de series temporales más avanzadas conocidas como Memoria a Corto y Largo Plazo (LSTM). Cubriremos el funcionamiento básico de LSTM e implementaremos para predecir los precios de las acciones en Python. Comencemos por comprender algunos detalles sobre el algoritmo.

¿Qué es LSTM y cómo funciona?

LSTM significa Memoria a Largo y Corto Plazo en inglés, que es una extensión de una red neuronal recurrente artificial (RNN) con la capacidad de almacenar y aprender información durante un período de tiempo. ¿Simple, ¿verdad? Por supuesto que no. Intentemos entender desde el principio. Todo comenzó con las Redes Neuronales (NNs), que condujeron al descubrimiento de las RNNs y finalmente las LSTMs.

  • Redes Neuronales Artificiales (ANN): Puedes considerar las ANN muy similares al cerebro. Básicamente, también consisten en neuronas que reciben información de neuronas anteriores, la procesan y luego la pasan a la siguiente neurona. Basándose en toda la información recopilada por estas neuronas, nuestro cerebro finalmente toma decisiones. Las ANN también funcionan de la misma manera, ya que estas neuronas aprenden los patrones dentro de los datos y se mejoran lo suficiente como para predecir el próximo resultado. Constituyen la base de todos los algoritmos de aprendizaje profundo y son el principal factor por qué el aprendizaje profundo se ha vuelto tan poderoso y popular en estos días.
  • Redes Neuronales Recurrentes (RNN): Las RNNs son la extensión de las ANN con una unidad de memoria interna, lo que significa que recuerda su entrada y la utiliza para aprender las entradas posteriores con mayor precisión. Esto hace que las RNN sean muy adecuadas para cualquier conjunto de datos secuenciales (o de series temporales).
  • Memoria a Largo y Corto Plazo (LSTM): Las LSTMs son una mejora sobre las RNNs. Operan en una arquitectura más sofisticada que resuelve todos los desafíos enfrentados anteriormente por las RNNs.
Modelo LSTM de redes neuronales

Modelo LSTM de redes neuronales. Fuente: https://pythoninoffice.com/

Un vistazo más cercano a LSTM

Como se muestra en la imagen anterior, una capa LSTM consta de un conjunto de bloques conectados recurrentemente (comúnmente conocidos como bloques de memoria). Estos bloques están conectados a múltiples bloques adicionales, y cada uno contiene tres puertas multiplicativas conocidas como la puerta de entrada, la puerta de salida y la puerta de olvido. Como sugiere el nombre, estas puertas funcionan de la siguiente manera:

  • La puerta de entrada controla qué nueva información se agregará desde la entrada actual.
  • Las puertas de salida deciden qué información emitir con base en la memoria actual.
  • La puerta de olvido decide qué información puede ser descartada.

Esta arquitectura hace que las LSTMs sean especiales ya que pueden retener información durante un largo período de tiempo y también decidir cuándo olvidar la información. Por lo tanto, podemos utilizar LSTM en diversas aplicaciones como la predicción de precios de acciones, reconocimiento de voz, traducción automática, generación de música, descripción de imágenes, etc.

Predicción de precios de acciones utilizando LSTM y Python

La mejor manera de aprender sobre cualquier algoritmo es probarlo. Por lo tanto, experimentemos con LSTM utilizándolo para predecir los precios de una acción. Solo para darte una idea sobre por qué LSTM puede ser útil en la predicción de precios de acciones u otros valores, recordemos la idea básica detrás del análisis técnico de los mercados que es que “la historia tiende a repetirse“. Entonces, si los patrones históricos son importantes para predecir el valor futuro de las acciones, los algoritmos LSTM son perfectos para este caso de uso.

Paso 1: Obtención de los datos de precios

Obtener cualquier precio de acciones ahora es simple utilizando la API de Yahoo Finance. Primero, vamos a configurar el entorno instalando todas las bibliotecas requeridas como se detalla a continuación.

#Instalación de bibliotecas usadas para el manejo de datos y creación del modelo
pip install numpy
pip install pandas
pip install sklearn
pip install yfinance

Una vez instaladas, importemos estos paquetes en nuestro entorno. Para este proyecto estoy utilizando Python 3.11. No recomiendo utilizar nada anterior a Python 3.8 al momento de escribir este artículo.

#Importación de bibliotecas
import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error
import yfinance as yf
from sklearn.preprocessing import MinMaxScaler 
from keras.models import Sequential
from keras.layers import Dense, LSTM, Dropout
import math
import warnings
warnings.filterwarnings('ignore')

Ahora vamos a cargar los datos de precios de Tesla (TSLA) utilizando la API de Yahoo Finance. Se han filtrado los datos desde el año 2013 hasta la actualidad, ya que más de 10 años de datos son suficientes para este experimento. Como se observa, tenemos el precio de la acción (apertura, cierre, máximo, mínimo) a nivel diario y el volumen negociado.

#Descarga de datos del mercado con yfinance
datos = yf.download('TSLA').reset_index()
datos = datos[(datos['Date'] >= "2014-01-01") & (datos['Date'] <= "2023-12-12")].reset_index(drop=True)

Preparación de datos para el modelado

Para el propósito del modelado, se hará el entrenamiento y la predicción del precio de cierre del activo analizado. Todos sabemos que las acciones de Tesla son muy volátiles, como se refleja en el gráfico de precios a continuación.

# Trazado de los precios de cierre a través del tiempo
plt.plot(datos["Date"], datos["Close"])
plt.title("Precio de la acción de Tesla")
plt.xlabel("Tiempo")
plt.ylabel("Precio")
plt.show()

Precio de acciones de Tesla

Para cualquier modelo de tipo lineal, necesitamos escalar o normalizar nuestros datos. Por lo tanto, utilizaremos MinMaxScaler, que transformará nuestros datos a valores que van desde 0 hasta 1.

# Escalado de los precios de cierre
# MinMaxScaler => y = (y - min(y))/(min(y) - max(y))
scaler = MinMaxScaler(feature_range=(0,1))
datos['valores_escalados'] = scaler.fit_transform(datos['Close'].values.reshape(-1,1))

Antes de invertir usando nuestros modelos, es muy importante realizar pruebas retrospectivas de los modelos utilizando datos históricos. Por lo tanto, dividiremos los datos en conjuntos de entrenamiento y validación (o prueba), que no son vistos por el modelo. Para simplificar, mantengamos los datos del último año para la prueba y el resto de los datos para el entrenamiento.

# División de los datos en conjuntos de entrenamiento y prueba
datos_entrenamiento = datos[datos['Date'] < '2023-01-01'] datos_prueba = datos[datos['Date'] >= '2023-01-01']
#Trazado de los datos de entrenamiento/datos de prueba
plt.figure(figsize=(10,6))
plt.grid(True)
plt.xlabel('Tiempo')
plt.ylabel('Precios de cierre')
plt.plot(datos_entrenamiento['Date'], datos_entrenamiento['Close'], 'green', label='Datos de entrenamiento')
plt.plot(datos_prueba['Date'], datos_prueba['Close'], 'blue', label='Datos de prueba')
plt.legend()
plt.show()
División de los datos de precios en datos de entrenamiento y datos de prueba

División de los datos de precios en datos de entrenamiento y datos de prueba

El modelo LSTM espera que los datos estén en un formato específico, generalmente un array tridimensional. En este artículo, simplemente vamos a utilizar el precio histórico para predecir el precio del día siguiente, pero también podemos agregar otros vectores externos para un mejor entrenamiento del modelo.

Además, hemos seleccionado un período retrospectivo de 60 días (el total de días en el pasado que se considerarán al pronosticar el precio del día siguiente). Ten en cuenta que puedes considerar cualquier otro período retrospectivo según tus datos u observaciones experimentales.

#Conversión de datos de entrenamiento a formato de matriz 3D
x_entrenamiento = []
y_entrenamiento = []

for i in range(60, len(datos_entrenamiento['valores_escalados'])):
    x_entrenamiento.append(datos_entrenamiento['valores_escalados'][i-60:i])
    y_entrenamiento.append(datos_entrenamiento['valores_escalados'][i])
    
x_entrenamiento = np.array(x_entrenamiento)
y_entrenamiento = np.array(y_entrenamiento)

Aquí, cada entrenamiento_x  son 60 días de precios de acciones y su variable correspondiente entrenamiento_y es el precio de las acciones del día siguiente.

Del mismo modo, transformemos también los datos de prueba en el formato requerido, que se utilizará para evaluar el rendimiento del modelo.

# Converción de los datos nuevamente a una matriz 3D según lo requiere LSTM
x_entrenamiento = np.reshape(x_entrenamiento, (x_entrenamiento.shape[0], x_entrenamiento.shape[1], 1))

x_prueba = []
y_prueba = datos_prueba['valores_escalados']

for i in range(60, len(datos_prueba)):
    x_prueba.append(datos_prueba['valores_escalados'][i-60:i])

x_prueba = np.array(x_prueba)
x_prueba = np.reshape(x_prueba, (x_prueba.shape[0], x_prueba.shape[1], 1))

Uso de la biblioteca Keras para el modelado

Para configurar el entrenamiento del modelo utilizando LSTM, utilizaremos la biblioteca Keras. Instalemos y carguemos los paquetes requeridos.

pip install keras
from keras.models import Sequential
from keras.layers import Dense, LSTM, Dropout

Hemos importado algunos módulos de Keras. Vamos a explicar cada módulo en detalle.

  • Sequential: Esta es la función base necesaria para inicializar cualquier arquitectura de red neuronal. Después de esto, podemos agregar múltiples capas de redes para el entrenamiento del modelo.
  • Dense: Dense es la red neuronal completamente conectada que vamos a utilizar como la capa final para obtener la salida final (precios).
  • LSTM: LSTM es la capa principal que vamos a ajustar y utilizar en nuestra arquitectura general.
  • Dropout: Dropout es una técnica donde seleccionamos aleatoriamente neuronas que serán ignoradas durante una iteración. Esto se utiliza para prevenir el sobreajuste durante el entrenamiento de modelos.

Definimos nuestra arquitectura personalizada con múltiples capas LSTM para un entrenamiento robusto.

# Definición de la arquitectura del modelo LSTM----------------------------------

# Inicialización del modelo
modelo = Sequential()

# Capa 1 del LSTM
modelo.add(LSTM(units = 50, return_sequences = True, input_shape = (x_entrenamiento.shape[1], 1)))
modelo.add(Dropout(0.25))

# Capa 2 del LSTM
modelo.add(LSTM(units = 50, return_sequences = True))
modelo.add(Dropout(0.25))

# Capa 3 del LSTM
modelo.add(LSTM(units = 50, return_sequences = True))
modelo.add(Dropout(0.25))

# Capa 4 del LSTM
modelo.add(LSTM(units = 50))
modelo.add(Dropout(0.25))

# Capa final
modelo.add(Dense(units = 1))
modelo.summary()

modelo lstm resultados

Como se observa, hemos utilizado cuatro capas LSTM en la arquitectura general, seguidas de múltiples capas de dropout para introducir aleatoriedad. Cada capa LSTM requiere los siguientes argumentos que deben ser definidos:

  • units“: son la dimensionalidad del espacio de salida (debemos experimentar con múltiples unidades para encontrar la mejor combinación para cada caso de uso).
  • return_sequence“: se establece en verdadero para que la salida de la capa sea otra secuencia de la misma longitud.
  • input_shape“: la forma de entrada para la primera capa será la forma de nuestros datos de entrenamiento.

Aquí, hemos utilizado 0.25 como Dropout, lo que significa que el 25% de las capas se eliminarán cada vez para evitar el sobreajuste. Esto es nuevamente un hiperparámetro que tendremos que ajustar para identificar la mejor combinación.

La capa Dense es la capa final que devolverá sólo una salida que será el precio de la acción.

Ahora vamos a compilar el modelo.

#Compilación del modelo
modelo.compile(optimizer = 'adam', loss = 'mean_squared_error')

En el modelo.compile, estamos utilizando el optimizador “Adam” (que es el optimizador más común). Sin embargo, siéntete libre de probar cualquier otro optimizador también. La función de pérdida utilizada aquí es “mean_squared_error” ya que estamos entrenando un modelo de regresión.

Entrenamiento del Modelo

Una vez hecho, finalmente ajustamos este modelo a nuestros datos de entrenamiento.

#Entrenamiento del modelo
modelo.fit(x_entrenamiento, y_entrenamiento, epochs = 10, batch_size = 32)

En el objeto modelo.fit, hemos pasado los siguientes argumentos:

  • x_entrenamiento: datos de entrenamiento (un array que contiene los precios históricos con un periodo retrospectivo de 60 días).
  • y_entrenamiento: precio de las acciones del día siguiente (que necesitamos predecir).
  • epochs: número total de iteraciones a realizar. Cada iteración significa “un pase completo sobre todo el conjunto de datos”. También es útil para el registro y la evaluación a lo largo del tiempo.
  • batch_size: otro hiperparámetro que define el número de muestras a procesar antes de actualizar los parámetros internos del modelo.

Basándonos en estos parámetros, el modelo se entrena durante los epochs definidos y muestra la pérdida total (mean_squared_error). Como se observa, la pérdida del modelo ha disminuido con el tiempo. Sin embargo, tomará algunos epochs más para converger.

Por ahora, usemos este modelo para predecir los datos de prueba no vistos y verifiquemos cómo se desempeña el modelo.

#Predicción con base en los datos de prueba
prediccion_precio_accion = modelo.predict(x_prueba)
prediccion_precio_accion = scaler.inverse_transform(prediccion_precio_accion)

Desempeño del modelo

#Trazado de las series en un mismo gráfico
plt.figure(figsize=(10,5), dpi=100)
plt.plot(datos_entrenamiento['Date'], datos_entrenamiento['Close'], label='Datos de entrenamiento')
plt.plot(datos_prueba['Date'], datos_prueba['Close'], color = 'blue', label='Precio actual del activo')
plt.plot(datos_prueba[60:]['Date'], prediccion_precio_accion, color = 'orange',label='Precio pronosticado del activo')

plt.title('Predicción del precio de un activo')
plt.xlabel('Tiempo')
plt.ylabel('Precio del activo')
plt.legend(loc='upper left', fontsize=8)
plt.show()
Precio pronosticado por el modelo

Predicción de precios de acciones utilizando LSTM

Como se observa, nuestro modelo puede capturar bastante bien la tendencia.

Sin embargo, para comparar la precisión/error real del modelo, podemos utilizar las siguientes métricas de rendimiento para comprender el rendimiento real del modelo.

#Comparación de la precisión/error real del modelo
datos_y_verdaderos = datos_prueba[60:]['Close'].values
datos_y_predichos = prediccion_precio_accion

#Reporte de desempeño
mse = mean_squared_error(datos_y_verdaderos, datos_y_predichos)
print('MSE: '+str(mse))
mae = mean_absolute_error(datos_y_verdaderos, datos_y_predichos)
print('MAE: '+str(mae))
rmse = math.sqrt(mean_squared_error(datos_y_verdaderos, datos_y_predichos))
print('RMSE: '+str(rmse))
mape = np.mean(np.abs(datos_y_predichos - datos_y_verdaderos)/np.abs(datos_y_verdaderos))
print('MAPE: '+str(mape))

El pronóstico puntual de nuestro modelo nos está dando un MAPE (error porcentual absoluto medio) del 19%, lo cual es mediocre. Definitivamente podemos mejorar más el modelo ya que sabemos que solo los precios históricos no impulsan el mercado de valores. Muchos factores internos/externos definen el mercado, los cuales pueden ser capturados en el tensor 3D utilizado por las LSTMs.

Resumen

Esa fue una descripción general del flujo de trabajo para utilizar LSTM y Python para predecir los precios de las acciones. Espero que te resulte útil. Sin embargo, debes hacer tu propia investigación y pruebas antes de invertir dinero real basado en estos algoritmos.


 

TagsPython
Raul Canessa

Leave a reply