# Importaciones necesarias
import yfinance as yf
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import Dense, LSTM, Dropout
from keras.callbacks import ModelCheckpoint
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# Guardar/cargar modelos
import joblib
# Estilo de graficacion
# from jupyterthemes import jtplot
# jtplot.style()
plt.style.use('seaborn')
Realizaremos un ajuste de la tendencia de los precios de apertura (Open
) de las acciones de Alphabet (google) para agosto del 2022 hasta el 31 de octubre del 2022, para lo cual utilizaremos 4 características:
Open
. Daremos los mismos precios con los cuales estaremos haciendo las predicciones, debido a que utilizamos una red neuronal recurrente.High
: Precio más alto alcanzado ese día.Low
: Precio más bajo alcanzado ese día.Close
: Precio de cierre.Definimos los conjuntos de entrenamiento:
# Datos
goog = yf.Ticker('aapl')
data = goog.history(interval='1d', start='2016-01-01', end='2021-05-10').reset_index()
# Instanciamos y configuramos el escalador. Escalaremos los datos
# al intervalo [0,1]
sc = MinMaxScaler(feature_range = (0,1))
# Preconjunto de entrenamiento: Precios del 2016-01-01 al 2021-05-10'
training_set = data.loc[:, ['Open', 'High', 'Low', 'Close']].values
# Realizamos el escalado de los datos
training_set_escaled = sc.fit_transform(training_set)
# Definicion del conjunto de entrenamiento
x_train = []
y_train = []
for i in range(60, data.shape[0]):
x_train.append(training_set_escaled[i-60:i, 0:4])
y_train.append(training_set_escaled[i,0])
# Convertimos las listas anteriores en arrays
x_train, y_train = np.array(x_train), np.array(y_train)
print('Número total de registros: ', data.shape[0])
print('Forma del array x_train: ', x_train.shape)
Número total de registros: 1346 Forma del array x_train: (1286, 60, 4)
donde:
x_train
es una lista de arrays. Cada array contempla un día fijo y considera los 60 días anteriores a ese día fijo, lo anterior debido a que la red estará "viendo" la información de 60 días atrás para cada día. Luego, y_train
el valor del día siguiente dependiendo del array en x_train
; por ejemplo, el último array en x_train
tiene enla última fila los precios para el 10 de mayo del 2021, entonces la entrada correspondiente a ese array en y_train
tiene el precio de apertura para la fecha del 11 de mayo del 2021, y así sucesivamente.
Después, definimos la arquitectura de la red neuronal y la entrenamos con los datos anteriores:
# Arquitectura de la red:
# Guardaremos el mejor modelo
path = 'best_model.hdf5'
c = ModelCheckpoint(filepath = path, monitor = "loss", save_best_only = True, save_freq="epoch")
callbacks = [c]
model = Sequential()
model.add(LSTM(units=60, return_sequences=True, input_shape=(x_train.shape[1], x_train.shape[2])))
model.add(Dropout(rate=0.2))
model.add(LSTM(units=60, return_sequences=True))
model.add(Dropout(rate=0.2))
model.add(LSTM(units=60, return_sequences=True))
model.add(Dropout(rate=0.2))
model.add(LSTM(units=60, return_sequences=False))
model.add(Dropout(rate=0.2))
model.add(Dense(units=1))
# Compilacion del modelo
model.compile(optimizer='adam', loss='mean_squared_error')
# Entrenamiento
model.fit(x_train, y_train, epochs=140, batch_size=32, callbacks=callbacks, verbose=False)
<keras.callbacks.History at 0x18fe2407760>
# Valor de la funcion de perdida sobre el conjunto de entrenamiento:
model.evaluate(x_train, y_train)
41/41 [==============================] - 3s 28ms/step - loss: 1.6686e-04
0.00016686141316313297
# Con el siguiente codigo podemos cargar el mejor modelo
# obtenido en el entrenamiento
model = Sequential()
model.add(LSTM(units=60, return_sequences=True, input_shape=(x_train.shape[1], x_train.shape[2])))
model.add(Dropout(rate=0.2))
model.add(LSTM(units=60, return_sequences=True))
model.add(Dropout(rate=0.2))
model.add(LSTM(units=60, return_sequences=True))
model.add(Dropout(rate=0.2))
model.add(LSTM(units=60, return_sequences=False))
model.add(Dropout(rate=0.2))
model.add(Dense(units=1))
model.load_weights('best_model.hdf5')
model.compile(optimizer='adam', loss='mean_squared_error')
model.evaluate(x_train, y_train)
41/41 [==============================] - 4s 32ms/step - loss: 2.3006e-04
0.00023006116680335253
goog.history(interval='1d', start='2021-05-11', end='2022-11-02').shape
(374, 7)
Procedemos a definir una función mediante la cual ajustaremos la tendencia y haremos la predicción del precio de la acción para el día siguiente
def index_predict(clave, fecha_inicio, fecha_fin, fecha_pred, c1, c2, c3):
"""Función que calcula las predicciones de los precios de apertura
para la compañía de clave @clave, de la fecha @fecha_inicio a la fecha
@fecha_fin. Con esas predicciones se realiza el ajuste de la tendencia
de la serie de tiempo de los precios de apertura.
El parámetro @fecha_pred debe ser la fecha inmediata siguiente (en días hábiles)
de la fecha @fecha_fin, con lo cual compararemos el precio de predicción calculado
por la red y el precio real de ese día. Lo anterior lo haremos mediante un dataframe
el cual albergará los precios reales y los precios predichos. Realmente, para el
el fin de la predicción, sólo nos interesará el último registro de dicho dataframe,
en el cual tenemos el precio real para la fecha @fecha_pred y el precio predicho
para ese día.
Para los demás parámetros:
* c1: color del histograma.
* c2: color del gráfico de líneas para las diferencias (df['diff']); además
color del gráfico de la serie de tiempo.
* c3: color de la línea punteada para el gráfico de líneas de las diferencias;
además, color del ajuste de la tendencia hecha por la red."""
# Obtencion de la informacion
goog = yf.Ticker(clave)
# Instanciamos y configuramos
sc = MinMaxScaler(feature_range = (0,1))
# Conjunto de prueba:
# Preeliminares
dataset_test = goog.history(interval='1d', start=fecha_inicio, end=fecha_fin)
inputs = dataset_test.loc[:, ['Open', 'High', 'Low', 'Close']].values
# Escalado de los datos
# inputs = inputs.reshape(-1, 1)
inputs = sc.fit_transform(inputs)
# Definicion del conjunto de prueba. La explicacion es totalmente analoga
# a la dada para x_train
x_test = []
for i in range(60, dataset_test.shape[0]+1):
x_test.append(inputs[i-60:i, 0:4])
x_test = np.array(x_test)
# Reshape necesario para poder ingresar x_test a la red
x_test = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], x_test.shape[2]))
# Valores reales del @fecha_inicio hasta @fecha_pred
df_real = goog.history(interval='1d', start=fecha_inicio, end=fecha_pred)
# Escogemos los registros a partir de la fila 60. La red predecira los precios
# a partir de la fila 60 y hasta la fecha en @fecha_pred. Asi, df_pruebas
# y precio_pred tienen el mismo numero de filas
df_pruebas = pd.DataFrame(df_real.values[60:, 0:4]).rename(columns={0: 'Open'})
# Valores predichos:
# EL ultimo array de x_test tiene como ultimo registro el precio para la fecha
# @fecha_fin, de modo que se predecira el precio del dia inmediato siguiente, el
# cual corresponde a la fecha @fecha_pred
precio_pred = model.predict(x_test)
# auxiliar para regresar la transformacion del escalado. Lo cual es necesario pues
# el esacalado se ajusto sobre los datos de las 4 columnas. Como precio_pred
# solo tiene una columna, lo que haremos a continuacion sera agregar 3 columnas
# de ceros
extends = np.zeros((len(precio_pred), 4))
extends[:,2] = precio_pred[:,0]
# Regresamos a la escala original y solo consideramos la columna para
# precio_pred
precio_pred = sc.inverse_transform(extends)[:,2]
precio_pred = precio_pred.reshape(-1,1)
# Creamos un dataframe:
# compararemos los precios reales con los predichos
df = pd.DataFrame(df_pruebas['Open'].values).rename(columns={0: 'real'})
df['pred'] = precio_pred
# Calculamos las diferencias entre los precios reales y las predicciones
df['diff'] = df['real'] - df['pred']
# Calculamos el porcentaje de error
df['error'] = (abs(df['diff']) * 100) / df['real']
# Mensaje de salida:
df_s = df.describe()
print()
print('Información de los errores:')
print(f'Valor real: {df.iloc[-1][0]}\nValor predicho: {df.iloc[-1][1]}')
print(f'Error promedio: {df_s.iloc[1][3]}')
print(f'Error máximo: {df_s.iloc[-1][3]}\nError mínimo: {df_s.iloc[3][3]}')
print(f'Cuartil 25%: {df_s.iloc[4][3]}\nCuartil 50%: {df_s.iloc[5][3]}')
print(f'Cuartil 75%: {df_s.iloc[6][3]}')
print()
# values
values = [df.iloc[-1][0], df.iloc[-1][1], df.iloc[1][3], df.iloc[-1][3],
df.iloc[3][3], df.iloc[4][3], df.iloc[5][3], df.iloc[6][3]]
# Graficos:
# * Histograma de la frecuencia del porcentaje de los errores
plt.figure(figsize=(14,8))
plt.subplot(2,2,1)
plt.hist(data=df, x='error', color=c1, rwidth=0.85)
plt.title('Histograma del porcentaje de los errores', size=18)
plt.ylabel('Frecuencia')
# * Grafico de lineas de las diferencias
plt.subplot(2,2,2)
plt.plot(df.values[-18:,2], color=c1)
plt.plot([0,17.5], [0,0], "x--", color=c3)
plt.title('Diferencias entre precios reales y predichos', size=18)
# * Grafico de la serie de tiempo con los valores reales y el ajuste
# de la tendencia calculado mediante las predicciones hechas por la red
plt.subplot(2,1,2)
plt.plot(df['real'], color=c2, label='Valores reales')
plt.plot(df['pred'], color=c3, label='Valores predichos')
plt.title('Ajuste de la tendencia en 60 días hasta el ' + fecha_fin, size=18)
plt.xlabel('Número-día')
plt.ylabel('Open ($)')
plt.legend()
plt.show()
# Regresamos el dataframe del contraste entre precios reales y predicciones.
return df
Tenemos entonces que toda la información de los precios hasta cierto día (digamos $k$) nos darán las predicciones para realizar el ajuste de la tendencia. Luego, el día $k$ nos dará la predicción para el precio de apertura del día $k+1$, de modo que el último registro del dataframe df
nos dará el contraste entre el precio real para el día $k+1$ con el precio predicho por la red.
df_google = index_predict('goog', '2021-05-11', '2022-11-01', '2022-11-02',
c1='#FF3206', c2='#FE5F00', c3='#FE8800')
10/10 [==============================] - 0s 33ms/step Información de los errores: Valor real: 95.58999633789062 Valor predicho: 94.21456609220274 Error promedio: 2.047309869861606 Error máximo: 12.139117906609947 Error mínimo: 0.0377950619278667 Cuartil 25%: 0.9805901546957925 Cuartil 50%: 1.7514528562013338 Cuartil 75%: 2.781113640880524
df_apple = index_predict('aapl', '2021-05-11', '2022-11-01', '2022-11-02',
c1='#0075FA', c2='#6606FF', c3='#00EDFA')
10/10 [==============================] - 0s 33ms/step Información de los errores: Valor real: 152.90634972825453 Valor predicho: 148.379840424398 Error promedio: 1.6883013265567774 Error máximo: 5.260354565219102 Error mínimo: 0.008761468537259438 Cuartil 25%: 0.9297047741548818 Cuartil 50%: 1.5841167190701282 Cuartil 75%: 2.371592810598173
df_microsoft = index_predict('msft', '2021-05-11', '2022-11-01', '2022-11-02',
c1='#C95CFF', c2='#FF5CE4', c3='#F9006A')
10/10 [==============================] - 0s 32ms/step Información de los errores: Valor real: 233.10307007565976 Valor predicho: 227.2700350437432 Error promedio: 1.705739315295022 Error máximo: 6.450203363443842 Error mínimo: 9.751466482708785e-05 Cuartil 25%: 0.9700206389952886 Cuartil 50%: 1.5266364994855026 Cuartil 75%: 2.308444211844514
df_meta = index_predict('meta', '2021-05-11', '2022-11-01', '2022-11-02',
c1='#064AFF', c2='#FF06EC', c3='#06D3FF')
10/10 [==============================] - 0s 34ms/step Información de los errores: Valor real: 98.22000122070312 Valor predicho: 105.6927645139308 Error promedio: 2.693205884777734 Error máximo: 26.034242222404306 Error mínimo: 0.013240309456469498 Cuartil 25%: 1.1775827982261753 Cuartil 50%: 2.2205602394150237 Cuartil 75%: 3.5304846161199888