1. Preparar datos de series temporales: del precio al retorno
Nivel 8: Series Temporales - Estacionariedad
En este nivel entramos en la metodología de predicción de series temporales univariantes apoyándonos en las herramientas principales del ecosistema Python. Pandas ofrece capacidades muy sólidas para manipular datos temporales, y Statsmodels aporta lo necesario para diseccionar los datos, estimar modelos estadísticos y ejecutar contrastes de hipótesis.
El investigador cuantitativo busca discernir tendencias, fluctuaciones estacionales y dependencias en datos financieros históricos aplicando técnicas estadísticas de series temporales. La aspiración final es generar señales de trading accionables. Mejorar la rentabilidad de nuestros algoritmos exige métodos estadísticos rigurosos que reconozcan comportamientos consistentes del activo, aprovechables como oportunidad. Eso implica examinar a fondo cómo evolucionan en el tiempo las propiedades del precio.
Para empezar, descargamos el S&P 500 (^GSPC) de Yahoo Finance. El objetivo de todo el nivel será entender y, más adelante, predecir este índice.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import datetime
import yfinance as yf
import warnings
import statsmodels.api as sm
import statsmodels.tsa.api as smt
import scipy.stats as scs
from statsmodels.tsa.stattools import coint, adfuller
warnings.filterwarnings('ignore')
plt.rc('figure', figsize=(18, 3))
pd.set_option('display.float_format', lambda x: '%.2f' % x)Cargamos casi siete años de historia diaria:
start = datetime.datetime(2016, 1, 1)
end = datetime.datetime(2022, 12, 30)
df = yf.download('^GSPC', start=start, end=end, auto_adjust=True)['Close']
df.head()
df.dropna(axis='index', how='all', inplace=True)
df.index[:5] # el índice ya viene en formato datetime, que es lo que queremosEse índice de tipo datetime no es un detalle cosmético: es lo que convierte un DataFrame corriente en una serie temporal. Pandas representa una serie temporal como un objeto Series, un array unidimensional con una etiqueta de tiempo por fila.
El segundo paso, y el más importante conceptualmente, es pasar del precio al retorno logarítmico:
Re = np.log(df).diff().dropna() # equivale a np.log(df / df.shift(1))
Re.head()La diferencia del logaritmo es la primera diferencia de la serie en escala logarítmica. Trabajamos con retornos y no con niveles de precio por tres motivos: los retornos son aproximadamente estacionarios mientras que los precios no lo son, son aditivos en el tiempo (la suma de retornos logarítmicos de varios días es el retorno logarítmico del periodo completo), y son comparables entre activos con precios de escala muy distinta. El .dropna() es obligatorio porque la primera diferencia siempre deja un valor nulo en la primera fila: no hay día anterior con el que compararla.
Con la serie ya creada, pandas permite consultarla usando el índice de fechas directamente, sin necesidad de posiciones numéricas:
ts = pd.Series(df, index=df.index)
df[:'2017'].tail(10) # todo hasta el final de 2017
df['2020-06':'2020-07'] # dos meses concretos
df.size # número de observaciones
df.describe() # estadísticos descriptivosOjo con describe() sobre precios: te devolverá una media y una desviación típica perfectamente calculadas y completamente inútiles. Si la serie tiene tendencia, esa media no describe ningún estado real del mercado, sino una mezcla de estados distintos en momentos distintos. Es la primera pista de por qué la estacionariedad, que veremos más adelante, va a ser el concepto central del nivel.