Acceder

📊Hablemos de Pautas Estacionales y donde encontrarlas​🔍​

Grandes estudiosos del mercado, hablan sobre el comportamiento cíclico de los mercados de valores, y por eso considero importante dar una lectura a la estacionalidad de algunos activos, hoy quiero mostrarles cómo identificar pautas estacionales con python.Pero antes de empezar, ¿Qué es una pauta...

Grandes estudiosos del mercado, hablan sobre el comportamiento cíclico de los mercados de valores, y por eso considero importante dar una lectura a la estacionalidad de algunos activos, hoy quiero mostrarles cómo identificar pautas estacionales con python.

Pero antes de empezar, ¿Qué es una pauta estacional? José Martínez, en un artículo para www.traders-mag.es en Diciembre 2016, titulado “Las pautas estacionales: Un comportamiento cíclico” Define la pauta estacional como “comportamiento bursátil de algunos subyacentes, dentro de un marco temporal concreto”.

Partiendo de esta definición es fácil imaginar que los Commodities como soya, trigo y maíz o gas natural y petróleo, tienen un comportamiento bursátil que responde a las condiciones climáticas que afecten los procesos productivos de los mismos.

¿El resto de activos financieros responde a pautas estacionales? Bueno, citando nuevamente a José Martínez, “La pauta estacional no hace referencia únicamente a las estaciones del año sino también a comportamientos excepcionales de la bolsa frente a un dato concreto, bien sea semanal, mensual, anual, etc.. Como por ejemplo el comportamiento de determinadas acciones, dentro de la publicación de algún dato regular como puede ser el dato de empleo, stocks, consumo, etc”. Y esto amigos míos, es lo que quiero que observemos, si aún no está convencido sobre porque ponerle un ojito a las pautas estacionales, lo invito a leer el artículo “Is Seasonality Is The Best Investment Strategy?” de Dimitri Speck (Spoiler Alert!!!), en su artículo Dimitri Speck muestra los resultados de un estudio de más de 200 años donde ¡La estacionalidad supera a todas las demás estrategias!

Sources: Guido Baltussen, Laurens Swinkels, Pim van Vliet Illustration: Seasonax.

Atención: La idea no es que abandone el resto de las estrategias, la intención es ponerle un ojito a la estacionalidad y bien lo dice Dimitri Speck “piensa por ti mismo en lugar de seguir a la manada. ¡Ten en cuenta la estacionalidad!”, existe toda una serie de documentación con respecto a la estacionalidad y si desea realizar una revisión no está de más que detalle documentación como la que aparece en el Stock Trader’s Almanac de Jeffrey A. Hirsch o paginas como https://charts.equityclock.com/



Estudio con Cuartiles y Diagrama de Bigotes y Cajas

¿Qué son los Cuartiles? y ¿Que es un Diagrama de Bigotes y Cajas?


En estadística, dada una distribución de datos estadísticos, los cuartiles son aquellos valores que la dividen en cuartos. El primer cuartil o cuartil inferior es aquel valor de la variable tal que la cuarta parte (25%) de las observaciones son inferiores o iguales a él, y el resto (75%) es superior o igual. El segundo cuartil es la mediana, ya que se trata del valor localizado en la mitad de la distribución. Finalmente, el tercer cuartil o cuartil superior es un valor tal que las tres cuartas partes de las observaciones son inferiores o iguales a él. Coinciden con los percentiles 25, 50 y 75 respectivamente.
Mientras que el Diagrama de bigotes y cajas es un método estandarizado para representar gráficamente una serie de datos numéricos a través de sus cuartiles. De esta manera, el diagrama de caja muestra a simple vista la mediana y los cuartiles de los datos.

Componentes del diagrama de caja. Fuente: https://es.wikipedia.org/wiki/Diagrama_de_caja
Componentes del diagrama de caja. Fuente: https://es.wikipedia.org/wiki/Diagrama_de_caja


Manos a la Obra

Tomaremos una serie de datos del contrato del Maíz, para determinar su estacionalidad a través del uso de cuartiles y el diagrama de bigotes y cajas. Y nos fijaremos en el comportamiento de los retornos observando el diagrama, ejecutamos el siguiente código:
1 | #Import necesary libraries
2 | from datetime import date
3 | import numpy as np
4 | import pandas as pd 
5 | import matplotlib.pyplot as plt 
6 | import seaborn as sns
7 | import yfinance as yf
8 | 
9 | 
10 | %matplotlib inline
11 | plt.rcParams["figure.figsize"] = (12, 6)
12 | sns.set()
13 | 
14 | #Descarga de Datos y Grafico de Quartiles Mensuales (incrementos mensuales medios por años)
15 | 
16 | start_date ='2010-01-01'
17 | today = date.today()
18 | end_date = today.strftime("%Y-%m-%d")
19 | data = yf.download("ZC=F", start=start_date, end=end_date)
20 | data.drop(['Open', 'Low', 'High', 'Volume', 'Close'], axis=1)
21 | 
22 | returns = np.log(data["Adj Close"]).diff().dropna().to_frame("Returns")
23 | Monthly_Returns = returns.groupby([returns.index.year.rename('year'), 
24 |                                    returns.index.month.rename('month')]).mean()
25 | Monthly_Returns.boxplot(column='Returns', by='month');



Bueno como podemos observar, para los meses de Junio (6), Julio (7) y Septiembre (9) tienen a tener grandes variaciones, ahora prestemos atención a lo siguiente la mediana demuestra que de haber invertido en Julio hasta Septiembre habríamos obtenidos retornos positivos, o bien de Noviembre a Diciembre, a excepción de un caso en Diciembre donde el retorno es inferior a la mediana de Noviembre, algo similar ocurre de Marzo a Abril, este es un primer vistazo.

También podemos observar la variación del Maíz por día en todos estos años bajo estudio, veamos como:
1 | def boxplot_group_day(returns_slice):
2 |     Daily_Returns = returns_slice.groupby([returns_slice.index.isocalendar().week.rename('week'), 
3 |                                  returns_slice.index.dayofweek.rename('day')]).mean()
4 |     return Daily_Returns.boxplot(column='Returns', by='day')
5 | 
6 | boxplot_group_day(returns);


No se observan grandes variaciones para este caso, los retornos tienden a mantenerse cercanos a 0 salvo algunos puntos atípicos. Y ¿qué tal si ahora observamos la variación de los retornos para un mes en específico?

Vamos:

1 | returns_of_june = returns.loc[returns.index.month.isin([6])]
2 | boxplot_group_day(returns_of_june);



Qué curioso pareciera que para el mes de Junio (6), si invertimos un lunes (0) y cerramos la operación un Martes (1) o Miércoles (2), obtendríamos retornos positivos. Ahora veamos el comportamiento para otro mes, que tal Diciembre (12).

Vamos:

1 | returns_of_june = returns.loc[returns.index.month.isin([6])]
2 | boxplot_group_day(returns_of_june);



Qué curioso pareciera que para el mes de Junio (6), si invertimos un lunes (0) y cerramos la operación un Martes (1) o Miércoles (2), obtendríamos retornos positivos. Ahora veamos el comportamiento para otro mes, que tal Diciembre (12).

1 | returns_of_december = returns.loc[returns.index.month.isin([12])]
2 | boxplot_group_day(returns_of_december);



Interesante, para diciembre no se obtienen mayores variaciones, aunque quizás exista una ventaja estadística operando de Miércoles a Viernes.

Pareciera que se podrían extraer algunas estrategias interesantes luego de estos estudios, antes de continuar veremos otro tipo de estudios, basado en los Mapas de Calor.

¿Qué es un mapa de calor? Un Mapa de Calor, es una técnica de visualización de datos que muestra la magnitud de un fenómeno como color en dos dimensiones. La variación de color puede deberse al tono o la intensidad, lo que proporciona al lector pistas visuales obvias sobre cómo el fenómeno se agrupa o varía en el espacio. Hay dos categorías fundamentalmente diferentes de mapas de calor: el mapa de calor del clúster y el mapa de calor espacial. En un mapa de calor de conglomerados, las magnitudes se presentan en una matriz de tamaño de celda fijo cuyas filas y columnas son fenómenos y categorías discretos, y la clasificación de filas y columnas es intencionada y algo arbitraria, con el objetivo de sugerir conglomerados o representarlos como descubierto mediante análisis estadístico. El tamaño de la celda es arbitrario pero lo suficientemente grande para ser claramente visible. Por el contrario, la posición de una magnitud en un mapa de calor espacial está forzada por la ubicación de la magnitud en ese espacio, y no hay noción de células; se considera que el fenómeno varía continuamente.
1 | md_returns = returns.squeeze().groupby([returns.index.month.rename('month'), 
2 |                                  returns.index.dayofweek.rename('day')]).mean().unstack(0)
3 | 
4 | fig, ax = plt.subplots(figsize=(16,6))
5 | sns.heatmap(md_returns, ax=ax, cmap="Spectral");


Pareciera buena estrategia comprar los lunes y vender los jueves del mes de Julio.
A continuación veremos otro tipo de estudio, eliminaremos la componente tendencial de nuestros datos para evitar tener datos con sesgos, muy bien hagámoslo:
1 | window = 253
2 | # detrend tome series by MA
3 | ratesM = data['Adj Close'].rolling(window).mean().dropna()
4 | ratesD = data['Adj Close'].reindex(ratesM.index).sub(ratesM)
5 | 
6 | fig, axs = plt.subplots(2, figsize=(16, 8), sharex=True)
7 | data['Adj Close'].plot(ax=axs[0], title="Trend")
8 | ratesM.plot(ax=axs[0])
9 | ratesD.plot(ax=axs[1], c="g", title="Residuals");


Ahora bien, actualicemos el estudio, vamos otra vez con los meses:

1 | Monthly_Returns = ratesD.groupby([ratesD.index.year.rename('year'), 
2 |                                   ratesD.index.month.rename('month')]
3 |                                 ).median().to_frame("Returns")
4 | Monthly_Returns.boxplot(by='month', figsize=(16, 8));



Como podemos observar, ya no queda tan claro que de Julio a Septiembre obtendremos retornos positivos, de hecho ya no están muchas cosas claras, se mantiene que obtengamos posibles incrementos de Noviembre a Diciembre pero no se ve tan claro.
Que podemos hacer en estos casos, bueno busquemos una confirmación más visual, incorporaremos otra librería a nuestro estudio.

Descomposición


Utilizaremos la librería statsmodels para hacer una descomposición estacional y lo graficaremos, de la siguiente manera:

1 | import statsmodels.api as sm
2 | decomposition = sm.tsa.seasonal_decompose(data['Adj Close'], model="additive", period = 253)
3 | 
4 | trend = decomposition.trend
5 | seasonal = decomposition.seasonal
6 | residual = decomposition.resid
7 | 
8 | fig, axs = plt.subplots(4, figsize=(14, 10), sharex=True)
9 | data['Adj Close'].plot(title='Original', color="blue", ax=axs[0])
10 | trend.plot(title='Trend', color="red", ax=axs[1])
11 | seasonal.plot(title='Seasonality', color="orange", ax=axs[2])
12 | residual.plot(title='Residuals', color="green", ax=axs[3])
13 | plt.tight_layout();seasonal["2019"].plot(label='Seasonality', color="blue", figsize=(20,8));



¿Que observamos acá? Bueno los precios de cierre de la serie da datos, la componente tendencia, la componente estacional y el residuo de residual que no es más que lo que le falta a la suma de la componente tendencial + la componente estacional para ser igual a la gráfica original, es importante destacar que en este caso es así porque se ha dicho que las componentes son “aditivas”.
Observemos la estacionalidad del año 2019.

1 | seasonal["2019"].plot(label='Seasonality', color="blue", figsize=(20,8));
Queríamos comprobar si de verdad de noviembre a diciembre ha incrementado el precio, bueno pareciera ser que sí.


Hagamos un Zoom a este periodo:

1 | seasonal["2019-11":"2019-12"].plot(label='Seasonality', color="blue", figsize=(20,8));



Pues pareciera que a partir del 15 de Noviembre hasta finales de Diciembre el precio recupera 15 puntos de su precio.
Bueno solo para verificar lo que hemos realizado, podemos compararlo con las gráficas que presenta https://charts.equityclock.com actualizada a Diciembre 2019.


Conclusión


Como podrá observar se encuentran cosas interesantes al realizar búsqueda de pautas estacionales, invito al lector aplicar el estudio a diversos activos y se sorprenderá gratamente, al identificar pautas estacionales podemos contar con una ventaja estadística a nuestro favor al momento de abrir operaciones. Invito al lector a que replique el estudio en otros activos.

Para ampliar la información:

Un agradecimiento especial para Antonio Rodríguez (@paduel_py) por el feedback en cuando al desarrollo del código que se presenta en este artículo.

Artículo escrito por Alexander Ríos en colaboración con Quantified Models.






5
¿Te ha gustado mi artículo?
Si quieres saber más y estar al día de mis reflexiones, suscríbete a mi blog y sé el primero en recibir las nuevas publicaciones en tu correo electrónico
  1. en respuesta a Gonzalo Smc
    -
    #5
    26/10/23 13:48
    Vuelta al cole ;)
  2. en respuesta a Gonzalo Smc
    -
    #4
    25/10/23 15:33
    Hola Gonzalo,

    Sí, la normalización es muy frecuente, la probaremos.

    Muchas gracias.
  3. en respuesta a Damianperp
    -
    #3
    25/10/23 15:13
    Los logaritmos y sus tablas, sí :)

    Es un truco numérico que se usa mucho en estadística y, a veces, también en finanzas.

    Hay un tipo de fenómenos que se pueden describir con una ley que se llama la Ley Normal. Entonces, es predecible la probabilidad de que tomen un valor determinado o, incluso, de que tomen un valor determinado si sabemos que antes tomaron otro.

    Una de las propiedades es que si su valor actual está a una distancia específica D, de su media M, podemos decir que existe una probabilidad del 32 por cien de que no se aleje más. Si la distancia es dos veces D, entonces podemos decir que la probabilidad de que siga alejándose es del 5% solo y la de que retroceda, del 95%. Y si se aleja 3 veces D, entonces la probabilidad de que siga en esa dirección es del 0.3 % (cero coma tres) por cien solo.

    Esto es muy útil cuando buscamos anticiparnos al final de un ciclo (como los ciclos estacionales). Porque podemos aproximar, con bastante exactitud, cuál será la esperanza o estimación objetiva promedio de que vaya en una u otra dirección con, tan solo, un dato y un par de medidas (la media y lo que usamos para calcular la distancia D, que se llama "desviación").

    El problema es que esto no es aplicable para las series de datos de cotizaciones de activos financieros, puesto que no tienen esa distribución. Una transformación logarítmica, cuadrática o de otros tipos puede aproximar la variable que estudiamos a la Ley Normal con una precisión aceptable. El problema de los logaritmos es que no funcionan con números negativos, así que hay que trasladar el dominio original a uno que sea mayor o igual de cero.
  4. en respuesta a Gonzalo Smc
    -
    #2
    25/10/23 15:01
    El palabro logaritmo me trae muy malos recuerdos de la BUP.
  5. #1
    25/10/23 12:30
    Hola.

    Una forma de suavizar las gráficas y que te salgan menos outliers es pasar por una función logarítmica el precio. Si tienes valores negativos, puedes estandarizar antes la serie entre 0 y 1, por ejemplo. Con numpy lo puedes hacer, se lo pasas a la serie de Pandas con un apply o directamente al array, como te venga mejor.

    Lo comento porque es más sencillo trabajar con desviaciones estándar (como si fuera una lognormal) que con rangos intercuartiles, que te harían falta si no transformaras el precio. No es perfecto, pero es más gestionable que los datos en crudo.

    Además que tener los datos distribuidos como una normal es bien en general.

    Yo suelo mirarlo a ojo, simplemente busco adónde se suelen agrupar las velas más largas de la temporalidad en la que estoy interesado.

    Un saludo.

Definiciones de interés
Proveedor de soluciones innovadoras de trading algorítmico e inteligencia artificial. Aportamos toda nuestra experiencia en el diseño, desarrollo e implantación de soluciones algorítmicas y de inteligencia artificial para inversores de todo tipo. TELEGRAM: t.me/quantifiedmodelstrading CONTACTO: [email protected]