Programa de Pós-graduação em Computação Aplicada – PPCA (UnB)
Análise Estatística de Dados e Informações
Professor: João Gabriel de Moraes Souza
Aluno: Angelo Donizete Buso Júnior
Medidas Descritivas Aplicadas à Finanças
Já que o mercado financeiro utiliza amplamente o comportamento passado para realizar projeções e tomar decisões, a estatística descritiva surge como uma ferramenta essencial para esse propósito. Ela permite analisar e resumir grandes volumes de dados históricos, como retornos diários, volatilidade e correlação entre ativos, fornecendo insights valiosos sobre risco e desempenho.
Por exemplo, a aplicação do Índice de Sharpe em uma carteira de ações permite identificar a melhor combinação entre retorno e volatilidade, enquanto gráficos de evolução patrimonial e histogramas de retornos ajudam a visualizar tendências e riscos potenciais, como o VaR (Value at Risk).
Além disso, ao explorar a correlação entre ativos, é possível criar portfólios diversificados e robustos, alinhados à fronteira eficiente de Markowitz. Assim, a estatística descritiva não apenas facilita a compreensão do passado, mas também apoia estratégias financeiras mais bem fundamentadas no presente e no futuro.
Problemática a ser simulada
O presente estudo tem como objetivo investigar o comportamento de cinco ações listadas na Bolsa de Valores de São Paulo (B3), bem como o índice que as representa.
Serão utilizadas ações classificadas como small caps. Para a B3, essas são ações de empresas de capital aberto com menor valor de mercado na bolsa de valores. O valor de mercado é calculado multiplicando a cotação da ação pelo número de papéis disponíveis.
A classificação de uma ação como small cap varia entre especialistas, geralmente abrangendo empresas com valor de mercado entre 300 milhões e 6 bilhões de reais. Comparadas às blue chips, que são ações de empresas consolidadas e de grande porte, as small caps possuem menor liquidez, o que pode dificultar a compra e venda rápida desses ativos.
No entanto, devido ao seu potencial de crescimento, as small caps podem oferecer retornos mais expressivos aos investidores. É importante que o investidor esteja ciente dos riscos e busque diversificar sua carteira para equilibrar possíveis oscilações.
As ações selecionadas para o estudo são:
| Papel | Setor | Descrição |
|---|---|---|
| VLID3 (Valid Soluções S.A.) | Tecnologia da Informação | A Valid é uma empresa brasileira especializada em soluções de segurança digital, identificação e certificação. Oferece serviços como emissão de documentos, certificação digital e soluções para meios de pagamento. Atua em segmentos como telecomunicações e bancos, com presença internacional. |
| CAML3 (Camil Alimentos S.A.) | Alimentos e Bebidas | A Camil é uma das maiores empresas de alimentos da América Latina, atuando nos segmentos de arroz, feijão, açúcar e pescados enlatados. Possui marcas reconhecidas e ampla distribuição no mercado brasileiro e internacional. |
| JHSF3 (JHSF Participações S.A.) | Construção Civil e Incorporação | A JHSF é uma empresa brasileira que atua nos setores de incorporação imobiliária, shopping centers, hotelaria e gastronomia. É conhecida por desenvolver projetos de alto padrão, como o complexo Cidade Jardim em São Paulo. |
| ALPA4 (Alpargatas S.A.) | Consumo Cíclico (Calçados e Vestuário) | A Alpargatas é uma empresa líder no setor de calçados, famosa por marcas como Havaianas e Osklen. Tem forte presença no mercado nacional e internacional, exportando produtos para diversos países. |
| LOGG3 (Log Commercial Properties e Participações S.A.) | Imobiliário | A LOG é especializada no desenvolvimento e locação de galpões logísticos e industriais, atuando em várias regiões do Brasil. Oferece soluções para empresas que necessitam de espaços para armazenamento e distribuição. |
Além dessas ações, utilizaremos o índice SMLL B3, que faz parte do portfólio da Bovespa e segue a seguinte metodologia para sua composição:
- Estar entre os ativos que, em ordem decrescente, estejam classificados fora da lista dos que representam 85% do valor de mercado de todas as empresas listadas no mercado à vista (lote-padrão) da B3.
- Estar entre os ativos elegíveis que, no período de vigência das 3 carteiras anteriores, em ordem decrescente de Índice de Negociabilidade (IN), representem em conjunto 99% do somatório total desses indicadores (ver Manual de Definições e Procedimentos dos Índices da B3).
- Ter presença em pregão de 95% no período de vigência das 3 carteiras anteriores.
- Não ser classificado como “Penny Stock” (ver Manual de Definições e Procedimentos dos Índices da B3).
Os dados históricos das ações serão coletados utilizando a biblioteca yfinance. Já os dados do índice SMLL serão obtidos por meio de compilações da B3 e do site Investing.com.
import pandas as pd
import yfinance as yf
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stats
from scipy import optimize
import plotly.express as px
from scipy.stats import norm
import warnings
warnings.filterwarnings("ignore")
Coleta de dados
Ações das empresas listadas em bolsa
tickers = ['VLID3.SA', 'CAML3.SA', 'JHSF3.SA', 'ALPA4.SA', 'LOGG3.SA']
dados_acoes = yf.download(tickers, start="2020-01-01", end="2024-11-15")['Adj Close']
# Ajustar o índice dos dados das ações para timezone-naive -> evitar problemas no joins
dados_acoes.index = dados_acoes.index.tz_localize(None)
dados_acoes.head()
[*********************100%***********************] 5 of 5 completed
| Ticker | ALPA4.SA | CAML3.SA | JHSF3.SA | LOGG3.SA | VLID3.SA |
|---|---|---|---|---|---|
| Date | |||||
| 2020-01-02 | 32.146427 | 7.718513 | 5.638634 | 28.244261 | 13.650017 |
| 2020-01-03 | 32.146427 | 7.760462 | 6.024435 | 27.318794 | 13.465775 |
| 2020-01-06 | 31.643213 | 7.601059 | 6.054113 | 28.104567 | 14.058558 |
| 2020-01-07 | 32.363499 | 7.433262 | 6.106046 | 28.375223 | 13.802219 |
| 2020-01-08 | 32.718704 | 7.483603 | 6.009596 | 27.947411 | 13.473783 |
dados_acoes.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 1216 entries, 2020-01-02 to 2024-11-14 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ALPA4.SA 1216 non-null float64 1 CAML3.SA 1216 non-null float64 2 JHSF3.SA 1216 non-null float64 3 LOGG3.SA 1216 non-null float64 4 VLID3.SA 1216 non-null float64 dtypes: float64(5) memory usage: 57.0 KB
O período da análise será entre as datas 02/01/2020 a 14/11/2024 e com base nos preços de fechamento diário de cada papel.
Índice SMALL B3
dados_smallcaps = pd.read_csv("/home/buso/mestrado/aedi-ppca/dados/small-cap-indice.csv", parse_dates=["Data"], dayfirst=True)
# Ajustar o índice dos dados do índice Small Caps para timezone-naive -> evitar problemas no joins
dados_smallcaps['Data'] = pd.to_datetime(dados_smallcaps['Data'])
dados_smallcaps.set_index('Data', inplace=True)
dados_smallcaps.rename(columns={"Último": "SMALL"}, inplace=True)
dados_smallcaps.head()
| SMALL | |
|---|---|
| Data | |
| 2024-11-15 | 1969.93 |
| 2024-11-14 | 1969.93 |
| 2024-11-13 | 1974.06 |
| 2024-11-12 | 1978.35 |
| 2024-11-11 | 1991.37 |
dados_smallcaps.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 1218 entries, 2024-11-15 to 2020-01-02 Data columns (total 1 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 SMALL 1218 non-null float64 dtypes: float64(1) memory usage: 19.0 KB
Dados a serem explorados
dados_acoes = dados_acoes.join(dados_smallcaps)
dados_acoes.head()
| ALPA4.SA | CAML3.SA | JHSF3.SA | LOGG3.SA | VLID3.SA | SMALL | |
|---|---|---|---|---|---|---|
| Date | ||||||
| 2020-01-02 | 32.146427 | 7.718513 | 5.638634 | 28.244261 | 13.650017 | 2894.58 |
| 2020-01-03 | 32.146427 | 7.760462 | 6.024435 | 27.318794 | 13.465775 | 2902.46 |
| 2020-01-06 | 31.643213 | 7.601059 | 6.054113 | 28.104567 | 14.058558 | 2899.97 |
| 2020-01-07 | 32.363499 | 7.433262 | 6.106046 | 28.375223 | 13.802219 | 2902.78 |
| 2020-01-08 | 32.718704 | 7.483603 | 6.009596 | 27.947411 | 13.473783 | 2884.84 |
Análise Gráfica de Preço
Plotamos os preços diários ao longo do tempo para cada ação e para o índice, permitindo a visualização clara de suas tendências e variações ao longo do período analisado.
dados_acoes.plot(title='Preços das Ações e Índice Small Caps', figsize=(12, 6))
plt.xlabel('Data')
plt.ylabel('Preço Ajustado (R$)')
plt.grid()
plt.show()
Grid Gráfica de Preço
fig, axes = plt.subplots(2, 3, figsize=(18, 10)) # 2 linhas, 3 colunas
# 1. Papéis e índice
ativos = tickers + ['SMALL']
# 2. Gráficos individuais para cada papel
for i, ticker in enumerate(tickers):
ax = axes[i // 3, i % 3] # Seleciona o subplot apropriado
dados_acoes[ticker].plot(ax=ax, title=f'Comportamento de {ticker}')
ax.set_xlabel('Data')
ax.set_ylabel('Preço Ajustado (R$)')
ax.grid()
# 3. Gráfico conjunto com índice Small Caps
ax = axes[1, 2]
dados_acoes[ativos].plot(ax=ax, title='Comportamento Conjunto das Ações e Índice Small Caps')
ax.set_xlabel('Data')
ax.set_ylabel('Preço Ajustado (R$)')
ax.grid()
plt.tight_layout()
plt.show()
Análise de Tendências¶
VLID3.SA
Tendência: Sugere uma tendência de alta no longo prazo. Desde 2020, o preço ajustado saiu de uma faixa baixa, cerca deR$ 5, para alcançar valores superiores aR$ 25nos últimos negócios.CAML3.SA
Tendência: Apresenta um movimento lateral (sem uma tendência clara de alta ou baixa) durante o período analisado. Os preços oscilam principalmente entreR$ 8eR$ 12, sem rompimentos significativos para cima ou para baixo.JHSF3.SA
Tendência: Indica uma possível tendência de baixa no longo prazo. Após atingir um pico em torno deR$ 8em 2020–2021, os preços caíram, estabilizando-se na faixa deR$ 3aR$ 4ao longo de 2024.ALPA4.SA
Tendência: Apresenta uma forte sugestão de tendência de baixa no período analisado. O ativo começou em patamares elevados (acima deR$ 60em 2020–2021), mas sofreu quedas acentuadas, fechando abaixo deR$ 10nos últimos pregões, indicando desvalorização significativa.LOGG3.SA
Tendência: Inicialmente sugere uma tendência de alta, com picos em torno deR$ 30no começo do período, seguido por uma correção acentuada. No longo prazo, apresenta uma tendência mais lateralizada, com preços oscilando entreR$ 20eR$ 25nos últimos anos.Índice SMALL
Tendência: O índice Small Caps indica um padrão de alta significativa até meados de 2021, seguido por uma queda prolongada e estabilização em patamares mais baixos. Sugere que as empresas de pequeno porte passaram por valorização até 2021, mas enfrentaram forte desvalorização posterior.
Análise Gráfica de Retorno
Calculamos o retorno diário de cada ação e do índice para entender suas variações de preço ao longo do tempo. O retorno diário mede a taxa de variação relativa do preço de um ativo de um dia para o outro, sendo uma métrica essencial para análise de volatilidade e comportamento de curto prazo.
Para o cálculo, utilizamos a fórmula do retorno logarítmico, definida como:
O uso do retorno logarítmico apresenta vantagens, como a possibilidade de somar retornos ao longo de um período, o que facilita análises agregadas e evita distorções em séries temporais.
Após o cálculo, os retornos diários de cada ação e do índice foram plotados para facilitar a visualização da volatilidade e do comportamento histórico.
retorno_log = np.log(dados_acoes / dados_acoes.shift(1))
# Plot dos retornos logarítmicos
retorno_log.plot(title='Retornos Logarítmicos das Ações e Índice Small Caps', figsize=(12, 6))
plt.xlabel('Data')
plt.ylabel('Retorno Logarítmico')
plt.grid()
plt.show()
Grid Gráfica dos Retornos
retorno_log.head()
| ALPA4.SA | CAML3.SA | JHSF3.SA | LOGG3.SA | VLID3.SA | SMALL | |
|---|---|---|---|---|---|---|
| Date | ||||||
| 2020-01-02 | NaN | NaN | NaN | NaN | NaN | NaN |
| 2020-01-03 | 0.000000 | 0.005420 | 0.066182 | -0.033315 | -0.013589 | 0.002719 |
| 2020-01-06 | -0.015778 | -0.020754 | 0.004914 | 0.028357 | 0.043080 | -0.000858 |
| 2020-01-07 | 0.022508 | -0.022323 | 0.008542 | 0.009584 | -0.018402 | 0.000969 |
| 2020-01-08 | 0.010916 | 0.006749 | -0.015922 | -0.015192 | -0.024084 | -0.006199 |
retorno_log.describe()
| ALPA4.SA | CAML3.SA | JHSF3.SA | LOGG3.SA | VLID3.SA | SMALL | |
|---|---|---|---|---|---|---|
| count | 1215.000000 | 1215.000000 | 1215.000000 | 1215.000000 | 1215.000000 | 1215.000000 |
| mean | -0.001282 | 0.000001 | -0.000195 | -0.000200 | 0.000482 | -0.000317 |
| std | 0.032848 | 0.024214 | 0.030979 | 0.029957 | 0.033628 | 0.019407 |
| min | -0.233994 | -0.137917 | -0.179385 | -0.203650 | -0.189039 | -0.182201 |
| 25% | -0.017035 | -0.013234 | -0.016891 | -0.015936 | -0.017142 | -0.009461 |
| 50% | -0.001367 | 0.000000 | -0.001436 | -0.000287 | -0.000966 | 0.000004 |
| 75% | 0.015105 | 0.013376 | 0.016740 | 0.014523 | 0.018368 | 0.009637 |
| max | 0.254025 | 0.188280 | 0.152254 | 0.230119 | 0.163678 | 0.103693 |
retorno_log.mean()*100
ALPA4.SA -0.128214 CAML3.SA 0.000122 JHSF3.SA -0.019485 LOGG3.SA -0.020042 VLID3.SA 0.048176 SMALL -0.031674 dtype: float64
retorno_descritivo = retorno_log.agg(['mean', 'std']).rename(index={'mean': 'média do retorno', 'std': 'desvio do retorno'})
retorno_descritivo
| ALPA4.SA | CAML3.SA | JHSF3.SA | LOGG3.SA | VLID3.SA | SMALL | |
|---|---|---|---|---|---|---|
| média do retorno | -0.001282 | 0.000001 | -0.000195 | -0.000200 | 0.000482 | -0.000317 |
| desvio do retorno | 0.032848 | 0.024214 | 0.030979 | 0.029957 | 0.033628 | 0.019407 |
fig, axes = plt.subplots(2, 3, figsize=(18, 10))
# 1. Papéis e índice
ativos = tickers + ['SMALL']
# 2. Gráficos individuais para cada papel
for i, ticker in enumerate(tickers):
ax = axes[i // 3, i % 3]
retorno_log[ticker].plot(ax=ax, title=f'Retorno Logarítmico de {ticker}')
ax.set_xlabel('Data')
ax.set_ylabel('Retorno Logarítmico')
ax.grid()
# 3. Gráfico conjunto com índice Small Caps
ax = axes[1, 2]
retorno_log[ativos].plot(ax=ax, title='Retorno Logarítmico Conjunto (Ações e Índice)')
ax.set_xlabel('Data')
ax.set_ylabel('Retorno Logarítmico')
ax.grid()
plt.tight_layout()
plt.show()
-
VLID3.SA
Observação: Os retornos apresentam volatilidade significativa no início, com amplitudes maiores, especialmente entre 2020 e 2021. Ao longo do tempo, sugere-se uma leve redução na volatilidade, com flutuações regulares próximas a zero.
-
CAML3.SA
Observação: Os retornos oscilam em torno de zero com amplitudes relativamente constantes durante o período analisado, sem indícios significativos de aumento ou redução na volatilidade.
-
JHSF3.SA
Observação: Embora haja picos de volatilidade mais expressivos no início do período, tornam-se menos frequentes no final.
-
ALPA4.SA
Observação: Apresenta maior volatilidade no início, com flutuações positivas e negativas expressivas. Com o tempo, observa-se uma redução nas amplitudes, embora os retornos continuem próximos a zero.
-
LOGG3.SA
Observação: Os retornos mostram dispersão significativa no início, com picos de volatilidade marcantes. Ao final do período, há uma redução na amplitude das oscilações, concentrando-se em valores próximos de zero.
-
Índice SMALL
Observação: O índice apresenta comportamento estável, com amplitudes constantes e sem variações estatisticamente significativas na volatilidade ao longo do período.
Em resumo, os retornos analisados, em sua maioria, não apresentam tendências claras de alta ou baixa. Alguns ativos, como VLID3.SA, ALPA4.SA e LOGG3.SA, sugerem uma leve redução na volatilidade ao longo do tempo. Já ativos como CAML3.SA e o índice SMALL demonstram retornos consistentes e oscilações predominantemente em torno de zero, sem mudanças significativas na volatilidade.
correlacao = retorno_log.corr()
correlacao = np.round(correlacao, 2)
correlacao
| ALPA4.SA | CAML3.SA | JHSF3.SA | LOGG3.SA | VLID3.SA | SMALL | |
|---|---|---|---|---|---|---|
| ALPA4.SA | 1.00 | 0.29 | 0.48 | 0.46 | 0.31 | 0.62 |
| CAML3.SA | 0.29 | 1.00 | 0.32 | 0.26 | 0.26 | 0.42 |
| JHSF3.SA | 0.48 | 0.32 | 1.00 | 0.57 | 0.47 | 0.75 |
| LOGG3.SA | 0.46 | 0.26 | 0.57 | 1.00 | 0.41 | 0.68 |
| VLID3.SA | 0.31 | 0.26 | 0.47 | 0.41 | 1.00 | 0.59 |
| SMALL | 0.62 | 0.42 | 0.75 | 0.68 | 0.59 | 1.00 |
custom_colorscale = [[0.0, 'green'], [0.5, 'blue'], [1.0, 'red']]
fig = px.imshow(correlacao,
text_auto=True,
aspect="auto",
color_continuous_scale=custom_colorscale,
labels=dict(color="Correlações"),
zmin=-1, zmax=1)
fig.show()
Entre os papéis analisados, JHSF3.SA, LOGG3.SA e ALPA4.SA apresentam as maiores correlações com o índice SMALL, sugerindo que seus retornos são fortemente influenciados por movimentos gerais do mercado de Small Caps.
Por outro lado, CAML3.SA é o ativo com menor correlação tanto em relação aos demais quanto ao índice SMALL, indicando um comportamento mais independente, o que pode ser útil para estratégias que busquem maior diversificação.
A análise da matriz de correlação revela que todos os ativos possuem relações positivas ou neutras entre si. Isso significa que, em geral, os movimentos de alta ou baixa de um ativo tendem a ser acompanhados por movimentos na mesma direção nos outros ativos.
Vale destacar que, em cenários de busca por correlações negativas, os ativos analisados não desempenham um papel significativo na diversificação de risco, uma vez que não há evidências de comportamentos inversamente relacionados. Essa característica limita o potencial de redução de risco em portfólios compostos exclusivamente por esses ativos.
Simulação de Carteiras e Fronteira Eficiente
Neste estudo, realizamos uma simulação de Monte Carlo para gerar 50.000 carteiras aleatórias, com o objetivo de identificar o portfólio ótimo baseado no maior índice de Sharpe. Para cada carteira simulada, foram calculados o retorno esperado, o risco (representado pelo desvio-padrão dos retornos) e o índice de Sharpe, resultando na construção da fronteira eficiente.
Metodologia
-
Cálculo dos retornos logarítmicos:
Os retornos logarítmicos das ações foram utilizados como base para os cálculos de retorno e risco. Esses retornos permitem uma análise consistente e agregável ao longo do tempo.
-
Configuração da simulação de Monte Carlo:
A simulação foi configurada para gerar 50.000 carteiras, com as seguintes etapas e variáveis:
- num_ports: Define o número de portfólios simulados (50.000).
- Arrays de resultados: Criados para armazenar os (i.) pesos dos ativos (
all_weights), os (ii.) retornos esperados (ret_arr), as (iii.) volatilidades (vol_arr) e os (iv.) índices de Sharpe (sharpe_arr) de cada portfólio.
Para cada uma das 50.000 iterações, o loop realiza os cálculos a seguir:
- Retorno esperado: Calculado como:
Retorno esperado = ∑ (Média dos retornos logarítmicos × Pesos)
- Volatilidade esperada (risco): Calculada com base na matriz de covariância dos retornos e os pesos do portfólio:
Volatilidade = √(PesosT ⋅ Matriz de Covariância ⋅ Pesos)
- Índice de Sharpe: Calculado como a razão entre o retorno esperado e a volatilidade:
Índice de Sharpe = Retorno esperado / Volatilidade
-
Identificação do portfólio ótimo:
O portfólio ótimo foi determinado como aquele com o maior índice de Sharpe. Extraímos os pesos dos ativos, o retorno esperado e o risco correspondentes a esse portfólio para análise detalhada.
Visualização dos Resultados
O gráfico gerado apresenta:
- Pontos coloridos: Cada ponto representa um portfólio simulado, com as cores indicando os respectivos índices de Sharpe.
- Ponto destacado: O portfólio ótimo, identificado pelo maior índice de Sharpe, é destacado no gráfico.
Com essa abordagem, foi possível mapear a fronteira eficiente e identificar as combinações de ativos que oferecem o melhor equilíbrio entre retorno esperado e risco, contribuindo para decisões mais fundamentadas na gestão de portfólios.
retorno_log.head()
| ALPA4.SA | CAML3.SA | JHSF3.SA | LOGG3.SA | VLID3.SA | SMALL | |
|---|---|---|---|---|---|---|
| Date | ||||||
| 2020-01-02 | NaN | NaN | NaN | NaN | NaN | NaN |
| 2020-01-03 | 0.000000 | 0.005420 | 0.066182 | -0.033315 | -0.013589 | 0.002719 |
| 2020-01-06 | -0.015778 | -0.020754 | 0.004914 | 0.028357 | 0.043080 | -0.000858 |
| 2020-01-07 | 0.022508 | -0.022323 | 0.008542 | 0.009584 | -0.018402 | 0.000969 |
| 2020-01-08 | 0.010916 | 0.006749 | -0.015922 | -0.015192 | -0.024084 | -0.006199 |
retorno_log_simul = retorno_log.drop(columns=['SMALL']).reset_index(drop=True)
retorno_log_simul
| ALPA4.SA | CAML3.SA | JHSF3.SA | LOGG3.SA | VLID3.SA | |
|---|---|---|---|---|---|
| 0 | NaN | NaN | NaN | NaN | NaN |
| 1 | 0.000000 | 0.005420 | 0.066182 | -0.033315 | -0.013589 |
| 2 | -0.015778 | -0.020754 | 0.004914 | 0.028357 | 0.043080 |
| 3 | 0.022508 | -0.022323 | 0.008542 | 0.009584 | -0.018402 |
| 4 | 0.010916 | 0.006749 | -0.015922 | -0.015192 | -0.024084 |
| ... | ... | ... | ... | ... | ... |
| 1211 | -0.082278 | -0.008889 | 0.002205 | 0.000857 | 0.080076 |
| 1212 | 0.040516 | 0.022700 | -0.022273 | -0.019015 | 0.026481 |
| 1213 | -0.001472 | -0.005000 | 0.000000 | -0.024736 | -0.053278 |
| 1214 | -0.001474 | -0.015152 | 0.002250 | -0.034580 | 0.015286 |
| 1215 | -0.001476 | -0.016678 | 0.000000 | 0.024693 | -0.021793 |
1216 rows × 5 columns
len(dados_acoes.columns)
6
len(retorno_log_simul.columns)
5
Identificação da Carteira com Melhor Índice de Sharpe
log_ret = retorno_log_simul
num_ports = 50000
all_weights = np.zeros((num_ports, len(retorno_log_simul.columns)))
ret_arr = np.zeros(num_ports)
vol_arr = np.zeros(num_ports)
sharpe_arr = np.zeros(num_ports)
for x in range(num_ports):
weights = np.array(np.random.random(5))
weights = weights/np.sum(weights)
# i. recepcionar todos os pesos
all_weights[x,:] = weights
# ii. retornos esperados
ret_arr[x] = np.sum((log_ret.mean() * weights))
# iii. volatilidade
vol_arr[x] = np.sqrt(np.dot(weights.T, np.dot(log_ret.cov(), weights)))
# iv. indice de Sharpe
sharpe_arr[x] = ret_arr[x]/vol_arr[x]
melhores_pesos = all_weights[sharpe_arr.argmax(),:]
print("Melhor índice de Sharpe simulado: {}". format(sharpe_arr.max()))
print("Local do melhor índice de Sharpe simulado: {}". format(sharpe_arr.argmax()))
print("Pesos do Portfólio para o máxino índice de Sharpe: {}".format(all_weights[sharpe_arr.argmax(),:]))
Melhor índice de Sharpe simulado: 0.011902279657440095 Local do melhor índice de Sharpe simulado: 43169 Pesos do Portfólio para o máxino índice de Sharpe: [0.00356932 0.14814608 0.04613269 0.06390184 0.73825008]
Durante a análise, utilizamos três operações principais para identificar e compreender o portfólio ótimo com base no Índice de Sharpe.
Primeiro, a função sharpe_arr.max() foi aplicada para obter o maior valor do array sharpe_arr, representando o maior Índice de Sharpe entre todas as carteiras simuladas.
Em seguida, utilizamos sharpe_arr.argmax() para localizar a posição (índice) correspondente ao maior valor no array, identificando assim a carteira ótima.
Por fim, acessamos a matriz all_weights com a expressão all_weights[sharpe_arr.argmax(), :], que retorna o vetor de pesos dos ativos dessa carteira específica. Esse vetor representa a melhor distribuição de pesos entre os ativos, resultando no maior Índice de Sharpe alcançado.
max_sr_ret = ret_arr[sharpe_arr.argmax()]
max_sr_vol = vol_arr[sharpe_arr.argmax()]
print(max_sr_ret*100)
print(max_sr_vol*100)
0.03294699203967357 2.7681245095832088
Gráfico da Simulação feita e identificação da carteira com Melhor Índice de Sharpe
plt.figure(figsize=(12,8))
plt.scatter(vol_arr, ret_arr, c=sharpe_arr, cmap='RdBu')
plt.colorbar(label='Sharpe Ratio')
plt.xlabel('Volatilidade')
plt.ylabel('Retorno')
plt.scatter(max_sr_vol, max_sr_ret,c='blue', marker='*', s=200, label= 'Melhor Carteira') # black dot
plt.legend()
plt.show()
print(log_ret.mean())
ALPA4.SA -0.001282 CAML3.SA 0.000001 JHSF3.SA -0.000195 LOGG3.SA -0.000200 VLID3.SA 0.000482 dtype: float64
Fronteira Eficiente de Markowitz
Construção da Fronteira Eficiente
Para construir a fronteira eficiente e identificar a carteira ótima, seguimos os seguintes passos:
Funções Utilizadas
- get_ret_vol_sr(weights): Calcula o retorno esperado, a volatilidade (risco) e o Índice de Sharpe (relação entre retorno e risco) para uma carteira com pesos definidos.
-
neg_sharpe(weights):
Calcula o Índice de Sharpe negativo, permitindo sua maximização ao usar a função de otimização
optimize.minimize(), que minimiza o valor retornado. -
Configuração para otimização:
- cons: Restrições que garantem que a soma dos pesos seja igual a 1.
- bounds: Limites que garantem que os pesos estejam entre 0 e 1.
- init_guess: Palpite inicial com pesos uniformes (20% para cada ativo, considerando 5 ativos).
-
Otimização da carteira com melhor Sharpe:
Utilizamos
optimize.minimize()com o métodoSLSQPpara encontrar os pesos que maximizam o Índice de Sharpe. O resultado, armazenado emop_results, fornece os pesos da carteira ótima.
Cálculo da Fronteira Eficiente
A fronteira eficiente foi gerada ao minimizar o risco para diferentes níveis de retorno esperado. O processo envolveu:
- frontier_y: Uma lista com valores de retorno esperado, utilizados como referência.
- minimize_volatility(weights): Função para minimizar a volatilidade da carteira enquanto mantém os pesos somando 1 e o retorno esperado igual ao valor atual de
frontier_y.
Visualização
O gráfico final exibe:
- Pontos coloridos: Carteiras simuladas, com as cores representando os respectivos Índices de Sharpe.
- Linha preta: A fronteira eficiente, representando as carteiras que otimizam o retorno para cada nível de risco.
- Ponto azul: A carteira ótima, identificada como a que possui o maior Índice de Sharpe.
Esse processo permitiu mapear as carteiras mais eficientes em termos de risco e retorno, facilitando a identificação de estratégias de alocação mais eficazes.
def get_ret_vol_sr(weights):
weights = np.array(weights)
ret = np.sum(log_ret.mean() * weights)
vol = np.sqrt(np.dot(weights.T, np.dot(log_ret.cov(), weights)))
sr = ret/vol
return np.array([ret, vol, sr])
def neg_sharpe(weights):
return get_ret_vol_sr(weights)[2] * -1
def check_sum(weights):
return np.sum(weights)-1
def minimize_volatility(weights):
return get_ret_vol_sr(weights)[1]
cons = ({'type': 'eq', 'fun': check_sum})
bounds = ((0,1), (0,1), (0,1), (0,1), (0,1))
init_guess = ((0.2),(0.2),(0.2),(0.2),(0.2))
op_results = optimize.minimize(neg_sharpe, init_guess, method="SLSQP", bounds= bounds, constraints=cons)
print(op_results)
message: Optimization terminated successfully
success: True
status: 0
fun: -0.014326218306554762
x: [ 0.000e+00 4.163e-17 5.037e-18 4.111e-17 1.000e+00]
nit: 10
jac: [ 4.248e-02 2.687e-03 1.199e-02 1.114e-02 1.164e-10]
nfev: 60
njev: 10
frontier_y = np.linspace(-0.0010, 0.0004, 100)
frontier_x = []
for possible_return in frontier_y:
cons = ({'type':'eq', 'fun':check_sum},
{'type':'eq', 'fun': lambda w: get_ret_vol_sr(w)[0] - possible_return})
result = optimize.minimize(minimize_volatility,init_guess,method='SLSQP', bounds=bounds, constraints=cons)
frontier_x.append(result['fun'])
plt.figure(figsize=(12,8))
plt.scatter(vol_arr, ret_arr, c=sharpe_arr, cmap='RdBu')
plt.colorbar(label='Sharpe Ratio')
plt.xlabel('Volatilidade')
plt.ylabel('Retorno')
plt.plot(frontier_x, frontier_y, linewidth=3, c='black')
plt.scatter(max_sr_vol, max_sr_ret, c='blue', marker='*', s=250, label= 'Melhor Carteira')
plt.legend()
plt.show()
A carteira ótima, representada pela estrela azul no gráfico, apresenta uma volatilidade relativamente elevada, condizente com o comportamento típico das ações classificadas como small caps. Essa característica é atribuída, em grande parte, à menor liquidez dessas ações, sua maior sensibilidade às condições econômicas e ao elevado potencial de crescimento ou declínio em comparação com empresas mais consolidadas, como as blue chips.
No gráfico, observa-se que a carteira com o maior Índice de Sharpe está posicionada à direita da fronteira eficiente. Isso indica que, para alcançar o melhor equilíbrio entre retorno esperado e risco, foi necessário assumir um nível de volatilidade mais alto do que o observado em carteiras com menores retornos esperados.
Simulação da Evolução do Patrimônio
Realizamos a simulação da evolução de um investimento inicial de R$ 35.000 aplicado na carteira ótima ao longo do período analisado. A seguir, descrevemos as etapas realizadas para a simulação:
- Aplicação dos pesos ótimos: Os pesos obtidos na simulação de Monte Carlo, que maximizam o Índice de Sharpe, foram utilizados para definir a alocação inicial do portfólio entre os ativos.
- Ajuste do dataset: Os preços das ações foram normalizados com base no valor inicial (base 1.0). Posteriormente, os valores normalizados foram ponderados pelos pesos da carteira e multiplicados pelo montante inicial do investimento.
- Cálculo do valor total do portfólio: A soma dos valores ponderados das ações foi realizada diariamente, resultando no valor total do portfólio ao longo do período de análise.
- Cálculo da taxa de retorno composta: Com base no valor total do portfólio, foi calculada a taxa de retorno composta (logarítmica), permitindo uma análise precisa do desempenho acumulado.
-
Resultados obtidos: A simulação resultou em:
- Um dataset ajustado com os valores diários do portfólio;
- Uma série temporal com as datas correspondentes;
- Os pesos das ações no portfólio;
- O valor final do portfólio ao término do período analisado.
def alocacao_ativos(dataset, dinheiro_total, seed = 0, melhores_pesos = []):
dataset = dataset.copy()
if seed != 0:
np.random.seed(seed)
# 1 busca os melhores pesos pela simulação de monte carlo
if len(melhores_pesos) > 0:
pesos = melhores_pesos
# print(f'passo 1 {pesos}')
else:
pesos = np.random.random(len(dataset.columns) - 1)
print(pesos, pesos.sum())
pesos = pesos / pesos.sum()
print(pesos, pesos.sum())
# 2
datas = dataset['Date']
# print(f'passo 2 {datas}')
dataset.drop(labels = ['Date'], axis = 1, inplace = True)
# 3 fator de normalização relativo ao valor inicial da série de dados.
colunas = dataset.columns
# print(f'passo 3 {colunas}')
for i in colunas:
dataset[i] = (dataset[i] / dataset[i][0])
# 4 aplica a distribuição do peso dos papeis da carteira
for i, acao in enumerate(dataset.columns):
# print(f'passo 4 {i, acao}')
dataset[acao] = dataset[acao] * pesos[i] * dinheiro_total
# 5 Identificar as colunas não numéricas
colunas_nao_numericas = dataset.select_dtypes(exclude=['number']).columns
# Exibir as colunas e tipos de dados das colunas não numéricas
# print("Colunas não numéricas e exemplos: passo 5")
for col in colunas_nao_numericas:
print(f"{col}: Tipo -> {dataset[col].dtype}, Exemplos -> {dataset[col].unique()[:5]}")
# Selecionar apenas as colunas numéricas para somar
dataset = dataset[dataset.select_dtypes(include=['number']).columns]
# o valor total que a "aplicação inicial" deu/daria
dataset['soma valor'] = dataset.sum(axis = 1)
dataset['taxa retorno'] = 0.0
for i in range(1, len(dataset)):
dataset['taxa retorno'][i] = np.log(dataset['soma valor'][i] / dataset['soma valor'][i - 1]) * 100
acoes_pesos = pd.DataFrame(data = {'Ações': colunas, 'Pesos': pesos})
return dataset, datas, acoes_pesos, dataset.loc[len(dataset) - 1]['soma valor']
dados_acoes.head()
| ALPA4.SA | CAML3.SA | JHSF3.SA | LOGG3.SA | VLID3.SA | SMALL | |
|---|---|---|---|---|---|---|
| Date | ||||||
| 2020-01-02 | 32.146427 | 7.718513 | 5.638634 | 28.244261 | 13.650017 | 2894.58 |
| 2020-01-03 | 32.146427 | 7.760462 | 6.024435 | 27.318794 | 13.465775 | 2902.46 |
| 2020-01-06 | 31.643213 | 7.601059 | 6.054113 | 28.104567 | 14.058558 | 2899.97 |
| 2020-01-07 | 32.363499 | 7.433262 | 6.106046 | 28.375223 | 13.802219 | 2902.78 |
| 2020-01-08 | 32.718704 | 7.483603 | 6.009596 | 27.947411 | 13.473783 | 2884.84 |
acoes_port = dados_acoes.copy()
acoes_port = acoes_port.drop(columns= ['SMALL']).reset_index()
acoes_port
| Date | ALPA4.SA | CAML3.SA | JHSF3.SA | LOGG3.SA | VLID3.SA | |
|---|---|---|---|---|---|---|
| 0 | 2020-01-02 | 32.146427 | 7.718513 | 5.638634 | 28.244261 | 13.650017 |
| 1 | 2020-01-03 | 32.146427 | 7.760462 | 6.024435 | 27.318794 | 13.465775 |
| 2 | 2020-01-06 | 31.643213 | 7.601059 | 6.054113 | 28.104567 | 14.058558 |
| 3 | 2020-01-07 | 32.363499 | 7.433262 | 6.106046 | 28.375223 | 13.802219 |
| 4 | 2020-01-08 | 32.718704 | 7.483603 | 6.009596 | 27.947411 | 13.473783 |
| ... | ... | ... | ... | ... | ... | ... |
| 1211 | 2024-11-08 | 6.530000 | 7.840000 | 4.540000 | 23.360001 | 25.340000 |
| 1212 | 2024-11-11 | 6.800000 | 8.020000 | 4.440000 | 22.920000 | 26.020000 |
| 1213 | 2024-11-12 | 6.790000 | 7.980000 | 4.440000 | 22.360001 | 24.670000 |
| 1214 | 2024-11-13 | 6.780000 | 7.860000 | 4.450000 | 21.600000 | 25.049999 |
| 1215 | 2024-11-14 | 6.770000 | 7.730000 | 4.450000 | 22.139999 | 24.510000 |
1216 rows × 6 columns
melhores_pesos
array([0.00356932, 0.14814608, 0.04613269, 0.06390184, 0.73825008])
dataset, datas, acoes_pesos, soma_valor = alocacao_ativos(acoes_port, 35000, 0, melhores_pesos)
dataset
| ALPA4.SA | CAML3.SA | JHSF3.SA | LOGG3.SA | VLID3.SA | soma valor | taxa retorno | |
|---|---|---|---|---|---|---|---|
| 0 | 124.926075 | 5185.112668 | 1614.644063 | 2236.564369 | 25838.752825 | 35000.000000 | 0.000000 |
| 1 | 124.926075 | 5213.293509 | 1725.119727 | 2163.279906 | 25489.993893 | 34716.613110 | -0.812973 |
| 2 | 122.970507 | 5106.210157 | 1733.618096 | 2225.502472 | 26612.100407 | 35800.401639 | 3.074078 |
| 3 | 125.769649 | 4993.488394 | 1748.489389 | 2246.934822 | 26126.864231 | 35241.546485 | -1.573343 |
| 4 | 127.150034 | 5027.305724 | 1720.870473 | 2213.057838 | 25505.152616 | 34593.536685 | -1.855882 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 1211 | 25.376608 | 5266.725152 | 1300.046135 | 1849.796864 | 47967.267064 | 56409.211823 | 6.682764 |
| 1212 | 26.425871 | 5387.645065 | 1271.410784 | 1814.954758 | 49254.471328 | 57754.907806 | 2.357585 |
| 1213 | 26.387008 | 5360.773724 | 1271.410784 | 1770.610356 | 46698.992699 | 55128.174572 | -4.654741 |
| 1214 | 26.348148 | 5280.160662 | 1274.274251 | 1710.428593 | 47418.310817 | 55709.522471 | 1.049017 |
| 1215 | 26.309285 | 5192.829685 | 1274.274251 | 1753.189228 | 46396.121532 | 54642.723983 | -1.933503 |
1216 rows × 7 columns
acoes_pesos
| Ações | Pesos | |
|---|---|---|
| 0 | ALPA4.SA | 0.003569 |
| 1 | CAML3.SA | 0.148146 |
| 2 | JHSF3.SA | 0.046133 |
| 3 | LOGG3.SA | 0.063902 |
| 4 | VLID3.SA | 0.738250 |
soma_valor
np.float64(54642.723982526964)
figura = px.line(title = 'Evolução do patrimônio')
for i in dataset.drop(columns = ['soma valor', 'taxa retorno']).columns:
figura.add_scatter(x = datas, y = dataset[i], name = i)
figura.show()
- VLID3.SA (linha azul-clara) demonstrou um desempenho destacado em relação aos demais ativos, indicando que, provavelmente, recebeu um peso significativo na "melhor carteira" e foi o principal responsável pelo crescimento do portfólio.
- CAML3.SA, JHSF3.SA, LOGG3.SA e ALPA4.SA apresentaram contribuições visivelmente menores, sugerindo que esses ativos possuem pesos reduzidos no portfólio ou retornos mais modestos em comparação à VLID3.SA. Apesar de terem um impacto menor no crescimento do patrimônio, esses ativos desempenham um papel crucial na diversificação do portfólio, ajudando a mitigar a volatilidade geral.
- Essa composição é consistente com o conceito do Índice de Sharpe, que busca maximizar o retorno ajustado ao risco, equilibrando a relação entre desempenho e volatilidade.
- O crescimento do portfólio foi impulsionado principalmente por VLID3.SA, enquanto os demais ativos contribuíram para a diversificação e estabilização da carteira.
- A escolha dos pesos resultou em uma carteira eficiente, que maximizou o retorno esperado dentro do nível de risco assumido, demonstrando a efetividade da metodologia aplicada.
figura = px.line(x = datas, y = dataset['soma valor'],
title = 'Evolução do patrimônio da Carteira',
labels=dict(x="Data", y="Valor R$"))
figura.add_hline(y = dataset['soma valor'].mean(),
line_color="green", line_dash="dot", )
figura.add_hline(y = 35000,
line_color="red", line_dash="dot", )
figura.show()
dataset['soma valor'].mean()
np.float64(29762.86634806909)
-
Linha Azul (Evolução do patrimônio):
Representa o comportamento do valor total da carteira ao longo do tempo, refletindo as variações decorrentes dos retornos dos ativos alocados. Inicialmente, observou-se uma forte queda no patrimônio, atingindo valores abaixo de R$ 20 mil. Esse período foi seguido por uma fase de estagnação até 2023. A partir desse ano, o patrimônio apresentou uma recuperação consistente, superando o investimento inicial de R$ 35 mil e alcançando valores superiores a R$ 50 mil na data mais recente.
-
Linha Vermelha (Valor inicial - R$ 35 mil):
Indica o ponto de equilíbrio em relação ao montante investido. Durante grande parte do período analisado, o patrimônio permaneceu abaixo desse valor, refletindo oscilações negativas na carteira. Apenas na parte final do intervalo, o patrimônio ultrapassou o valor inicial, evidenciando uma recuperação significativa.
-
Linha Verde (Média do patrimônio):
Representa a média do valor acumulado ao longo de todo o período. A linha verde está posicionada abaixo do valor inicial (linha vermelha), o que sugere que, na maior parte do tempo, o patrimônio da carteira esteve inferior ao investimento inicial de R$ 35 mil.
-
Recuperação e Crescimento:
Apesar do início desafiador, a carteira demonstrou uma recuperação robusta a partir de 2023. O patrimônio final superou tanto o valor inicial quanto a média histórica, refletindo o impacto positivo da alocação de pesos baseada no Índice de Sharpe, que permitiu capturar o crescimento expressivo no final do período.
O que é VaR (Value at Risk)?
O Value at Risk (VaR) é uma medida estatística amplamente utilizada para quantificar o risco financeiro de um portfólio. Ele estima a perda máxima esperada dentro de um intervalo de confiança e horizonte de tempo especificados. Em outras palavras, o VaR responde à seguinte pergunta:
"Qual é a perda máxima que posso esperar, com um intervalo de confiança específico, durante um determinado período?"
Componentes do VaR
-
Nível de confiança:
Representa a probabilidade de que as perdas não excedam o valor calculado. Por exemplo, ao utilizar um nível de confiança de 90%, temos 90% de certeza de que as perdas ficarão abaixo do VaR calculado, e 10% de probabilidade de que as perdas sejam maiores.
-
Distribuição dos retornos:
Assume-se que os retornos seguem uma distribuição normal. Para o cálculo do VaR, utilizamos a média (μ) e o desvio-padrão (σ) dos retornos.
-
Fórmula do VaR:
A fórmula utilizada é:
VaR = - (μ + Z × σ)
Onde:
- μ: Média dos retornos.
- Z: Pontuação Z correspondente ao nível de confiança, obtida a partir da função inversa da distribuição normal (ex.:
norm.ppf). - σ: Desvio-padrão dos retornos.
O VaR é uma ferramenta poderosa para gestão de riscos, fornecendo um parâmetro claro e estatisticamente fundamentado sobre a exposição do portfólio a perdas em cenários adversos. No entanto, é importante lembrar que ele depende das premissas de distribuição normal dos retornos, o que pode não se aplicar a todos os cenários reais.
def var(returns, confidence_level):
returns = np.array(returns)
z_score = norm.ppf(confidence_level)
stdev = np.std(returns)
var = -(returns.mean() + z_score * stdev)
return var
# Calculate VaR at the 90% confidence level
confidence_level = 0.90
returns = dataset['taxa retorno']
value = dataset['soma valor']
var_90 = var(returns, confidence_level)
value_90 = var(value, confidence_level )
print(f'VaR no intervalo de confiança de 90% para os retornos é de {var_90:.2f} %')
print(f'VaR no intervalo de confiança de 90% para os retornos é de {value_90:.2f}' )
VaR no intervalo de confiança de 90% para os retornos é de -3.38 % VaR no intervalo de confiança de 90% para os retornos é de -40935.94
media = dataset['soma valor'].mean()
print("Media de ganho", media)
desvp = dataset['soma valor'].std()
print('desvio padrão do ganho', desvp)
Media de ganho 29762.86634806909 desvio padrão do ganho 8721.979187088387
returns = dataset['taxa retorno'] # Retornos diários
confidence_level = 0.90 # Nível de confiança
var_90 = var(returns, confidence_level) # Calculando o VaR
# Configuração do histograma
plt.figure(figsize=(12, 6))
hist_vals, bins, patches = plt.hist(returns, bins=50, color='skyblue', alpha=0.7, edgecolor='black', density=True)
# Linha do VaR
plt.axvline(x=var_90, color='red', linestyle='--', linewidth=2, label=f'VaR ({confidence_level*100:.0f}%) = {var_90:.2f}%')
# Destacar a área de risco (à esquerda do VaR)
bin_centers = 0.5 * (bins[:-1] + bins[1:]) # Posição dos centros das barras do histograma
plt.fill_betweenx(hist_vals, bins[:-1], var_90, where=(bins[:-1] <= var_90), color='red', alpha=0.2, label='Área de Risco')
# Títulos e legendas
plt.title('Distribuição dos Retornos e VaR', fontsize=14)
plt.xlabel('Retornos Diários (%)', fontsize=12)
plt.ylabel('Frequência (Densidade)', fontsize=12)
plt.legend(loc='upper left')
plt.grid(alpha=0.3)
# Exibir o gráfico
plt.show()
Conclusão
Este estudo explorou a construção e análise de portfólios financeiros utilizando ações classificadas como small caps, com enfoque na simulação de Monte Carlo e na maximização do Índice de Sharpe. A aplicação de métodos estatísticos e ferramentas de otimização permitiu identificar a "melhor carteira", caracterizada por um equilíbrio otimizado entre retorno esperado e risco. Apesar de a carteira ótima apresentar uma volatilidade elevada, característica típica de small caps, sua composição demonstrou potencial significativo de crescimento ao longo do período analisado.
A análise destacou a importância de ativos como VLID3.SA, que se mostrou o principal motor de crescimento do portfólio, e a contribuição diversificadora de outros ativos, como CAML3.SA e JHSF3.SA, que ajudaram a mitigar a volatilidade geral. Esses resultados reforçam a relevância da diversificação e da aplicação de técnicas baseadas no Índice de Sharpe para a construção de portfólios eficientes.
A simulação da evolução do patrimônio inicial de R$ 35.000 demonstrou que, mesmo enfrentando períodos iniciais desafiadores, a carteira obteve recuperação e crescimento robustos a partir de 2023, refletindo a efetividade dos pesos alocados. Além disso, a análise do VaR proporcionou uma visão clara sobre a exposição ao risco, quantificando as possíveis perdas máximas em cenários adversos com base em intervalos de confiança.
Por fim, a construção da fronteira eficiente e a identificação de portfólios ótimos evidenciaram a aplicabilidade prática de técnicas estatísticas e financeiras para a gestão de riscos e retornos. Este estudo oferece uma base sólida para futuras pesquisas no campo de finanças quantitativas, especialmente em cenários envolvendo small caps, e para decisões estratégicas relacionadas à alocação de ativos e mitigação de riscos.