QuantBrasil

Analisando a Sazonalidade do Varejo no Mercado Financeiro

Helio Quintanilha Jr.
Por Helio Quintanilha Jr.
03 novembro, 2021
Compartilhar:

No artigo de hoje, vamos abordar um tema que é muito falado e poucas vezes compreendido pelas pessoas: sazonalidade. Antes de tudo, porém, vamos entender o que sazonalidade significa e como ela pode ser aplicada no mercado financeiro.

O que é sazonalidade?

A palavra sazonal deriva-se do latim satio, que por sua vez vem do verbo serere, que significa semear. Não à toa o conceito de sazonalidade está muito atrelado à agricultura. Em outras palavras, sazonalidade é uma caracterista que se repete sempre em uma determinada época do ano.

Por exemplo, durante períodos festivos como Natal, dia das crianças ou até mesmo Black Friday, um aumento nas vendas de varejo é esperada. E esse fato se repete todos os anos.

Efeitos práticos da sazonalidade

No exemplo anterior, vimos um caso específico onde a sazonalidade possui um efeito positivo, ou seja, onde esperamos lucrar com o aumento das vendas no varejo. Contudo, esse nem sempre é o caso. Um clássico exemplo onde a sazonalidade pode interferir negativamente é no caso de uma empresa de sorvetes. Durante o inverno, devido ao clima frio, provavelmente essa venda vai ser baixa. Nesse caso, a sazonalidade implica em uma redução da receita da empresa.

Sazonalidade aplicada ao mercado financeiro

Alguns papéis da bolsa podem possuir "sazonalidade", ou seja, eles podem tender a performar melhor ou pior em determiados períodos do ano. Além dos papéis de varejo, que podem possuir uma sazonalidade durante períodos festivos, outro exemplo são as petroleiras, que são conhecidas por performarem bem no período de inverno do hemisfério norte.

No artigo de hoje, vamos colocar essa sazonalidade à prova e analisar se empresas de varejo performam melhor no último trimestre do ano. Para isso, escolhemos as lojas Renner (LREN3) e o Mercado Livre (MELI) como proxies representando o varejo físico e eletrônico respectivamente.

Definindo nossa hipótese

Existem diversas maneiras de se realizar esse estudo. Um ponto-chave é escolha do período de tempo que vamos utilizar para comparar os resultados. Aqui, vamos tentar provar a hipótese de um melhor último trimestre nas empresas de varejo comparando-o com a variação dos preços das ações no trimestre anterior.

Em outras palavras: vamos comparar as variações de preço dos ativos no 3º e 4º trimestres e observar se há alguma diferença significativa.

Para fortalecer ainda mais o nosso estudo, vamos comparar o retorno obtido pelas Lojas Renner no último trimestre com o índice Bovespa. Isso é importante uma vez que queremos capturar uma possível discrepância do varejo. Ou seja, se LREN3 tiver subido 5% e o IBOV tiver subido 15%, mesmo o ativo de varejo tendo um desempenho positivo, relativamente a performance foi pior.

Durante esse artigo, vamos tentar automatizar o código ao máximo possível. Dessa forma, se você quiser aplicar esse estudo para diferentes ativos em diferentes períodos de tempo, pequenas modificações serão necessárias.

E aí? Será que vamos encontrar alguma sazonalidade interessante no nosso estudo de hoje? Vamos conferir!

Importando as bibliotecas necessárias

Antes de começarmos, vamos importar todas as bibliotecas que iremos usar nese artigo.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

Estudo de caso: sazonalidade de Lojas Renner

Vamos iniciar nossa análise no varejo importando os dados dos últimos 15 anos de Lojas Renner (LREN3). O arquivo .csv estará disponível no nosso grupo do Telegram.

asset = pd.read_csv('../data/D1/LREN3-D1-sazonalidade.csv', index_col='datetime')
asset
close
datetime
2006-07-03 00:00:003.56
2006-07-04 00:00:003.42
2006-07-05 00:00:003.17
2006-07-06 00:00:003.31
2006-07-07 00:00:003.31
......
2020-12-22 00:00:0042.85
2020-12-23 00:00:0043.40
2020-12-28 00:00:0043.65
2020-12-29 00:00:0043.81
2020-12-30 00:00:0043.22

3584 rows × 1 columns

Para auxiliar nossa análise, vamos criar duas colunas no nosso dataframe. A primeira, chamada month, vai nos retornar o mês e ano de cada linha. Com essa informação, conseguimos identificar se estamos no último dia de cada mês comparando os valores com a linha posterior.

asset["month"] = pd.to_datetime(asset.index).strftime('%Y-%m')
asset["last of month"] = asset["month"] != asset["month"].shift(-1)
asset
closemonthlast of month
datetime
2006-07-03 00:00:003.562006-07False
2006-07-04 00:00:003.422006-07False
2006-07-05 00:00:003.172006-07False
2006-07-06 00:00:003.312006-07False
2006-07-07 00:00:003.312006-07False
............
2020-12-22 00:00:0042.852020-12False
2020-12-23 00:00:0043.402020-12False
2020-12-28 00:00:0043.652020-12False
2020-12-29 00:00:0043.812020-12False
2020-12-30 00:00:0043.222020-12True

3584 rows × 3 columns

Agora vamos criar uma nova coluna com o valor de fechamento do ativo no último dia do mês. O resto dos dias vai receber o valor 0.

asset["value"] = np.where(    
    (asset["last of month"] == True), 
    asset["close"],
    0    
)
asset
closemonthlast of monthvalue
datetime
2006-07-03 00:00:003.562006-07False0.00
2006-07-04 00:00:003.422006-07False0.00
2006-07-05 00:00:003.172006-07False0.00
2006-07-06 00:00:003.312006-07False0.00
2006-07-07 00:00:003.312006-07False0.00
...............
2020-12-22 00:00:0042.852020-12False0.00
2020-12-23 00:00:0043.402020-12False0.00
2020-12-28 00:00:0043.652020-12False0.00
2020-12-29 00:00:0043.812020-12False0.00
2020-12-30 00:00:0043.222020-12True43.22

3584 rows × 4 columns

Podemos agora reduzir o nosso dataframe selecionando apenas as colunas que tem o valor diferente de zero, ou seja, só estamos interessados no último dia de cada mês.

asset_filtered = asset[asset["value"] != 0].copy()
asset_filtered
closemonthlast of monthvalue
datetime
2006-07-31 00:00:003.482006-07True3.48
2006-08-31 00:00:003.522006-08True3.52
2006-09-29 00:00:003.482006-09True3.48
2006-10-31 00:00:003.712006-10True3.71
2006-11-30 00:00:003.932006-11True3.93
...............
2020-08-31 00:00:0043.022020-08True43.02
2020-09-30 00:00:0039.302020-09True39.30
2020-10-30 00:00:0037.122020-10True37.12
2020-11-30 00:00:0044.292020-11True44.29
2020-12-30 00:00:0043.222020-12True43.22

174 rows × 4 columns

Após essas manipulações, podemos facilmente calcular a diferença entre dois valores subsequentes utilizando a função pct_change.

asset_filtered["variation"] = asset_filtered["value"].pct_change()
asset_filtered
closemonthlast of monthvaluevariation
datetime
2006-07-31 00:00:003.482006-07True3.48NaN
2006-08-31 00:00:003.522006-08True3.520.011494
2006-09-29 00:00:003.482006-09True3.48-0.011364
2006-10-31 00:00:003.712006-10True3.710.066092
2006-11-30 00:00:003.932006-11True3.930.059299
..................
2020-08-31 00:00:0043.022020-08True43.020.057002
2020-09-30 00:00:0039.302020-09True39.30-0.086471
2020-10-30 00:00:0037.122020-10True37.12-0.055471
2020-11-30 00:00:0044.292020-11True44.290.193157
2020-12-30 00:00:0043.222020-12True43.22-0.024159

174 rows × 5 columns

Repare que podemos facilmente adicionar uma nova condição e filtrar apenas as variações em períodos de interesse. Por exemplo:

# example with 2 months for analysis
months = ["2007-09", "2007-10"]

pct_per_month = asset_filtered[asset_filtered["month"].isin(months)]
pct_per_month

closemonthlast of monthvaluevariation
datetime
2007-09-28 00:00:005.182007-09True5.180.079167
2007-10-31 00:00:006.062007-10True6.060.169884

E por fim, podemos calcular a média desses resultados:

pct_per_month["variation"].mean() 
0.12452541827541819

Para facilitar a nossa vida, já que vamos repetir a análise para diferentes ativos, vamos colocar todos os passos anteriores em uma função.

Definindo a nossa função

A função que retorna a média da variação mensal no período que escolhermos está programada abaixo.

def mean_return_per_months(df, months):
    
    # creating a month column
    df["month"] = pd.to_datetime(df.index).strftime('%Y-%m')
    
    # defining the last day of the month    
    df["last of month"] = df["month"] != df["month"].shift(-1)

    # if last day of the month, get the close price, else 0
    df["value"] = np.where(
        (df["last of month"] == True), 
        df["close"], 
        0
    )

    # get the non-zero values 
    df_filtered = df[df["value"] != 0].copy()
    
    # calculate the difference in percentage between the last day of the months
    df_filtered["variation"] = df_filtered["value"].pct_change()

    # filter the df to get only the list of months we are interrested on
    pct_per_month = df_filtered[df_filtered["month"].isin(months)]
    
    # return the mean of the results
    return pct_per_month["variation"].mean()   

Lojas Renner (LREN3)

Com os dados das Lojas Renner já carregados em nosso DataFrame e a função de cálculo da média de variação criada, podemos facilmente calcular os resultados.

Para isso, vamos criar uma lista: mean_of_semesters, onde vamos armazenar a média do terceiro e quarto trimestre desse ativo. Lembre-se, estamos interessados no último trimestre do ano, pois nele temos o Black Friday e o Natal. Como métrica, escolhemos para esse artigo comparar o último trimestre com o trimestre anterior.

Já que os nossos dados vão de 2006 até 2020, podemos criar um loop para alimentarmos a lista com as médias do período escolhido. Para cada ano, adicionamos a média do trimestre que estamos interessados na lista mean_of_semesters. Por fim, criamos um DataFrame a partir dessa lista.

Importante notar que é computacionalmente mais eficiente criar uma lista, adicionar itens à ela dentro do loop e após o processo transformar a lista num DataFrame, do que criar um DataFrame vazio ou com NaN e ir adicionando linhas a cada iteração.

mean_of_semesters = []

for year in range(2006, 2021):
    
    # third trimester 
    months =  [str(year) + "-07", str(year) + "-08", str(year) + "-09"]
    mean_3rd_quarter = mean_return_per_months(asset, months)  
    
    # fourth trimester
    months =  [str(year) + "-10", str(year) + "-11", str(year) + "-12"]
    mean_4th_quarter = mean_return_per_months(asset, months)
    
    mean_of_semesters.append((year, mean_3rd_quarter, mean_4th_quarter))

columns = ['year', 'mean_LREN_3rd_quarter', 'mean_LREN_4th_quarter']
df = pd.DataFrame(mean_of_semesters, columns=columns)

df
yearmean_LREN_3rd_quartermean_LREN_4th_quarter
020060.0000650.072331
120070.0085170.003820
22008-0.100010-0.087783
320090.1316990.094397
420100.059773-0.002123
52011-0.045369-0.012644
620120.0652360.057072
720130.002069-0.014300
820140.0037170.025179
92015-0.067250-0.021932
1020160.015849-0.011449
1120170.096676-0.003819
1220180.0229140.115833
1320190.0231490.036420
142020-0.0152210.037843

Com o novo DataFrame pronto, podemos facilmente plotar os resultados em forma de barra:

df.plot.bar(
  x='year',
  y=['mean_LREN_3rd_quarter','mean_LREN_4th_quarter'],
  title='LREN3',
  label=['3rd quarter mean','4th quarter mean']
)
plt.ylabel('return')
plt.show()

Algumas observações podem ser tiradas dessa análise.

Primeiramente, vimos que nos últimos 3 anos, a variação no quarto trimestre foi sempre positiva e maior do que a variação média do terceiro trimestre.

Outro ponto importante é que, quando houve um terceiro trimestre ruim com variação média negativa, o quarto trimestre fechou melhor (ou menos pior).

Contudo, podemos observar que historicamente existe um equilíbrio entre os trimestres. Enquanto que o quarto trimestre fechou 5 vezes acima do terceiro, em 6 oportunidades o oposto aconteceu.

Comparando com o IBOV

Para fortalecermos a nossa análise, vamos comparar os resultados de LREN3 com o nosso índice, IBOV, para o último trimestre do ano.

Para isso, vamos primeiramente baixar os dados pertinentes do IBOV (novamente, disponível no nosso grupo do Telegram.)

asset = pd.read_csv('../data/D1/IBOV-D1-sazonalidade.csv', index_col='datetime')[["close"]]
asset
close
datetime
2006-01-0233507.26
2006-01-0334540.57
2006-01-0435002.37
2006-01-0534936.10
2006-01-0635475.01
......
2021-10-14113185.50
2021-10-15114648.00
2021-10-18114428.20
2021-10-19110672.80
2021-10-20110786.40

3906 rows × 1 columns

Uma vez que já temos nossa função programada, só precisamos repetir o último passo da análise anterior e criarmos o loop para o período escolhido. Vamos também usar o comando pd.merge para juntarmos o DataFrame do IBOV com o DataFrame contendo os resultados de LREN3. Escolhemos a opção on='year' para juntarmos os resultados pelo ano.

mean_of_semesters = []

for year in range(2006, 2021):    
    
    # fourth trimester
    months =  [str(year) + "-10", str(year) + "-11", str(year) + "-12"]
    
    mean_4th_quarter = mean_return_per_months(asset, months)
    mean_of_semesters.append((year, mean_4th_quarter))

df = pd.merge(
    df,
    pd.DataFrame(mean_of_semesters,columns=['year','mean_IBOV_4th_quarter']),
    on='year'
)
df
yearmean_LREN_3rd_quartermean_LREN_4th_quartermean_IBOV_4th_quarter
020060.0000650.0723310.068591
120070.0085170.0038200.018335
22008-0.100010-0.087783-0.079875
320090.1316990.0943970.037608
420100.059773-0.002123-0.000154
52011-0.045369-0.0126440.029244
620120.0652360.0570720.010670
720130.002069-0.014300-0.004876
820140.0037170.025179-0.025021
92015-0.067250-0.021932-0.012530
1020160.015849-0.0114490.012913
1120170.096676-0.0038190.010104
1220180.0229140.1158330.035862
1320190.0231490.0364200.033853
142020-0.0152210.0378430.081705

A fim de compararmos os resultados de LREN3 com o IBOV, vamos calcular a diferença entre eles.

Para isso, criamos uma nova coluna no nosso dataFrame contendo a diferença entre o 4º trimestre de LREN3 com o 4º trimestre do IBOV.

df['diff_4th_quarter_LREN_IBOV'] = df['mean_LREN_4th_quarter'] - df['mean_IBOV_4th_quarter']
df
yearmean_LREN_3rd_quartermean_LREN_4th_quartermean_IBOV_4th_quarterdiff_4th_quarter_LREN_IBOV
020060.0000650.0723310.0685910.003741
120070.0085170.0038200.018335-0.014515
22008-0.100010-0.087783-0.079875-0.007909
320090.1316990.0943970.0376080.056788
420100.059773-0.002123-0.000154-0.001970
52011-0.045369-0.0126440.029244-0.041888
620120.0652360.0570720.0106700.046402
720130.002069-0.014300-0.004876-0.009424
820140.0037170.025179-0.0250210.050200
92015-0.067250-0.021932-0.012530-0.009402
1020160.015849-0.0114490.012913-0.024362
1120170.096676-0.0038190.010104-0.013923
1220180.0229140.1158330.0358620.079971
1320190.0231490.0364200.0338530.002567
142020-0.0152210.0378430.081705-0.043863


Novamente podemos plotar facilmente os resultados:

diff = df.copy().set_index('year')['diff_4th_quarter_LREN_IBOV']
diff.plot(
  kind="bar", 
  color=(diff > 0).map({True: '#49ce8b',False: '#d64242'}),
  title='Diferença de Retorno LREN3 vs IBOV (4º Tri)',
);

Nos últimos 6 anos, apenas em 2018 as Lojas Renner tiveram uma variação significativamente maior que o IBOV. Na nossa amostragem de 15 anos, LREN3 obteve uma variação média maior que o IBOV no último trimestre em 6 oportunidades.

No entanto, as maiores diferenças estão quando LREN3 bate o IBOV, chegando a quase 8% de diferença em 2018.

Estudo de caso: sazonalidade de Mercado Livre

Vamos finalizar a nossa análise analisando um ativo de varejo eletrônico, MELI. Os dados são os preços listados na NASDAQ, não a BDR, e também estarão disponíveis no nosso grupo do Telegram.

asset = pd.read_csv('../data/D1/MELI-D1-sazonalidade.csv', index_col='datetime')[['close']]
asset
close
datetime
2007-08-0917.440
2007-08-1027.616
2007-08-1330.669
2007-08-1429.099
2007-08-1527.675
......
2020-12-241690.480
2020-12-281663.720
2020-12-291673.490
2020-12-301712.940
2020-12-311675.220

3373 rows × 1 columns

Novamente, como já temos a nossa função programada, apenas repetimos o último passo para analisar o terceiro e quarto trimestre dos últimos 14 anos.

mean_of_semesters = []

for year in range(2007, 2021):
    
    # third trimester 
    months =  [str(year) + "-07", str(year) + "-08", str(year) + "-09"]
    mean_3rd_quarter = mean_return_per_months(asset, months)  
    
    # fourth trimester
    months =  [str(year) + "-10", str(year) + "-11", str(year) + "-12"]
    mean_4th_quarter = mean_return_per_months(asset, months)
    
    mean_of_semesters.append((year,mean_3rd_quarter, mean_4th_quarter))

columns = ['year', 'mean_MELI_3rd_quarter', 'mean_MELI_4th_quarter']
df = pd.DataFrame(mean_of_semesters, columns=columns)
df
yearmean_MELI_3rd_quartermean_MELI_4th_quarter
020070.2894420.307803
12008-0.146742-0.031678
220090.1287590.120161
320100.111936-0.024727
42011-0.1172430.155292
520120.037076-0.010898
620130.079529-0.068355
720140.0529780.065074
82015-0.1357540.087248
920160.096012-0.054002
1020170.0158790.072397
1120180.046667-0.043407
122019-0.0334420.014822
1320200.0354220.159825

E plotamos a variação média dos últimos trimestres também em um gráfico de barras.

df.plot.bar(
  x='year',
  y=['mean_MELI_3rd_quarter', 'mean_MELI_4th_quarter'],
  title='MELI',
  label=['3rd quarter mean', '4th quarter mean']
)
plt.ylabel('return')
plt.show()

Aqui também vemos um equilíbro, sendo que em 8 vezes a média do último trimestre foi maior que a do terceiro.

Contudo, a média de retorno do quarto trimestre foi positiva e maior que a do terceiro trimestre em 5 dos último 7 anos. Destaque para o ano passado, onde o retorno médio do último trimestre foi o segundo maior da série analisada.

Conclusão

Para esse nosso primeiro estudo de sazonalidade, analisamos o mercado de varejo onde escolhemos Lojas Renner (LREN3) e Mercado Livre (MELI) como os nossos ativos.

Para LREN3, observamos uma pequena vantagem do último trimestre em cima do terceiro, mais especificamente nos últimos 3 anos.

Contudo, vimos também que a média de retorno do índice Bovespa no último trimestre do ano foi positiva nos últimos 5 anos, e melhor que o retorno dos ativos LREN3 em 4 dos últimos 6 anos, perdendo em 2018 e 2019.

Já MELI mostrou uma média de retorno positiva no quarto trimestre e maior que o trimestre anterior em 5 dos últimos 7 anos, sinalizando um possível período positivo para esse papel no último trimestre.

Será que algum outro ativo de varejo performaria melhor que esses dois analisados hoje nesse período? Você consegue pensar em algum outro período melhor para comparar os resultados? Fique à vontade para brincar com o código, escolhendo seu ativo e período de preferência para fazer sua própria análise!

Para um próximo artigo, vamos analisar a hipótese de um aumento nas ações de petróleo durante o inverno do hemisfério norte. Por isso, não se esqueça de entrar no nosso grupo do Telegram e se inscrever na nossa newsletter abaixo para receber notificações sobre artigos novos!