Construção de Portfólio de Ações#

Estratégia para Refletir as Principais Participações em Setores do Ibovespa#

Resumo#

Este artigo propõe a criação de uma carteira fictícia como parte integrante de um estudo subsequente de análise de risco de portfólio de ações, composta por 10 ativos diversos. A abordagem adotada consiste na seleção da maior participação em cada setor, com a determinação dos pesos baseada na participação percentual do papel que compõe o índice Bovespa. Para tanto, utilizou-se a tabela “IBOVDia_12–01–24.csv”, que contém dados atualizados do Ibovespa em 12/01/2024, empregando a linguagem de programação Python. Optou-se por esse método visando incorporar as principais ações do mercado brasileiro com o maior número possível de setores para garantir a diversificação na carteira.

📌 Nota: Este portfólio de ações será utilizado em estudos futuros para simular a análise de riscos em carteiras de investimentos. A metodologia ou os resultados obtido não devem ser considerados como recomendação de compra ou venda de ativos.

Índice Bovespa (Ibovespa B3)#

O Ibovespa é uma carteira teórica de ativos composta por ações e units de empresas que atendem a critérios específicos, representando aproximadamente 80% dos negócios e do volume financeiro no mercado de capitais brasileiro. Criado em 1968, o índice tornou-se uma referência global ao longo de 50 anos sendo o principal indicador de desempenho das ações na B3, reunindo as empresas mais importantes do mercado brasileiro.

Descrição e coleta dos dados#

Importação das bibliotecas que serão utilizadas:

import pandas as pd
import numpy as np

Lendo os dados da Carteira Teórica do IBovespa:

csv = './data/IBOVDia_12-01-24.csv'
ibov = pd.read_csv(csv,
                   sep=';',
                   encoding='ISO-8859-1',
                   skipfooter=2,
                   engine='python',
                   thousands='.',
                   decimal=',',
                   header=1,
                   index_col=False)
ibov.head()
Setor Código Ação Tipo Qtde. Teórica Part. (%) Part. (%)Acum.
0 Bens Indls / Máqs e Equips WEGE3 WEG ON NM 1481593024 2.316 2.316
1 Bens Indls / Mat Transporte EMBR3 EMBRAER ON NM 734632705 0.722 0.722
2 Bens Indls/Transporte AZUL4 AZUL PN N2 327593725 0.209 2.157
3 Bens Indls/Transporte CCRO3 CCR SA ON NM 995335937 0.613 2.157
4 Bens Indls/Transporte GOLL4 GOL PN N2 198184909 0.071 2.157

Primeiramente, é fundamental obtermos uma visão abrangente dos rótulos das colunas, examinarmos os tipos de dados presentes e checarmos se há valores ausentes em nossa base.

ibov.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 87 entries, 0 to 86
Data columns (total 7 columns):
 #   Column          Non-Null Count  Dtype  
---  ------          --------------  -----  
 0   Setor           87 non-null     object 
 1   Código          87 non-null     object 
 2   Ação            87 non-null     object 
 3   Tipo            87 non-null     object 
 4   Qtde. Teórica   87 non-null     int64  
 5   Part. (%)       87 non-null     float64
 6   Part. (%)Acum.  87 non-null     float64
dtypes: float64(2), int64(1), object(4)
memory usage: 4.9+ KB

Para o entendimento dos dados, os metadados da tabela fornecerão informações sobre o significado de cada coluna.

Colunas:

Setor:          (Tipo: Texto) - O setor e subsetor ao qual a ação pertence (separados por "/").
Código:         (Tipo: Texto) - O código identificador único usado na bolsa de valores (B3) para identificar e negociar um determinado ativo.
Ação:           (Tipo: Texto) - O nome da ação.
Tipo:           (Tipo: Texto) - O tipo de ação seguido pelo Nivel de Governança (separados por " ").
Qtde. Teórica:  (Tipo: Numérico) - A quantidade teórica de ações no índice Bovespa.
Part. (%):      (Tipo: Numérico) - A participação percentual da ação no índice Bovespa.
Part. (%)Acum.: (Tipo: Numérico) - A participação percentual do Setor / SubSetor da ação no índice Bovespa.

Conforme observado nos metadados, a coluna “Setor” apresenta o setor e subsetor da ação separados por “/”. Podemos identificá-los utilizando a função unique().

ibov['Setor'].unique()
array(['Bens Indls / Máqs e Equips', 'Bens Indls / Mat Transporte',
       'Bens Indls/Transporte', 'Cons N  Básico / Alimentos Processados',
       'Cons N Cíclico / Bebidas', 'Cons N Cíclico / Comércio Distr.',
       'Cons N Cíclico / Pr Pessoal Limp', 'Cons N Ciclico/Agropecuária',
       'Consumo Cíclico / Comércio', 'Consumo Cíclico / Tecid Vest Calç',
       'Consumo Cíclico/Constr Civil', 'Consumo Cíclico/Viagens e Lazer',
       'Diversos', 'Financ e Outros / Explor Imóveis',
       'Financ e Outros / Holdings Divers',
       'Financ e Outros / Interms Financs',
       'Financ e Outros / Previd  Seguros',
       'Financeiro e Outros/Serviços Financeiros Diversos',
       'Mats Básicos / Madeira e Papel', 'Mats Básicos / Mineração',
       'Mats Básicos / Químicos', 'Mats Básicos / Sid Metalurgia',
       'Petróleo/ Gás e Biocombustíveis', 'Saúde/Comércio Distr.',
       'Saúde/SM Hosp An.Diag', 'Tec.Informação/Programas Servs',
       'Telecomunicação', 'Utilidade Públ / Água Saneamento',
       'Utilidade Públ / Energ Elétrica'], dtype=object)

Vamos separar os dados desta coluna em “Setor” e “Subsetor”.

# Separando os dados antes e depois da "/".
# O parâmetro 'expand=True' retorna os dados em colunas separadas.
ibov[['Setor', 'Subsetor']] = ibov['Setor'].str.split('/', expand=True)

# Removendo os espaços extras ao redor das strings
ibov['Setor'] = ibov['Setor'].str.strip()
ibov['Subsetor'] = ibov['Subsetor'].str.strip()

Agora, as informações de Setor e Subsetor das ações estão separadas em colunas distintas.

ibov['Setor'].unique()
array(['Bens Indls', 'Cons N  Básico', 'Cons N Cíclico', 'Cons N Ciclico',
       'Consumo Cíclico', 'Diversos', 'Financ e Outros',
       'Financeiro e Outros', 'Mats Básicos', 'Petróleo', 'Saúde',
       'Tec.Informação', 'Telecomunicação', 'Utilidade Públ'],
      dtype=object)
ibov['Subsetor'].unique()
array(['Máqs e Equips', 'Mat Transporte', 'Transporte',
       'Alimentos Processados', 'Bebidas', 'Comércio Distr.',
       'Pr Pessoal Limp', 'Agropecuária', 'Comércio', 'Tecid Vest Calç',
       'Constr Civil', 'Viagens e Lazer', None, 'Explor Imóveis',
       'Holdings Divers', 'Interms Financs', 'Previd  Seguros',
       'Serviços Financeiros Diversos', 'Madeira e Papel', 'Mineração',
       'Químicos', 'Sid Metalurgia', 'Gás e Biocombustíveis',
       'SM Hosp An.Diag', 'Programas Servs', 'Água Saneamento',
       'Energ Elétrica'], dtype=object)

Novamente, iremos verificar se as novas colunas possuem dados faltantes.

ibov[['Setor', 'Subsetor']].info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 87 entries, 0 to 86
Data columns (total 2 columns):
 #   Column    Non-Null Count  Dtype 
---  ------    --------------  ----- 
 0   Setor     87 non-null     object
 1   Subsetor  81 non-null     object
dtypes: object(2)
memory usage: 1.5+ KB

Observa-se que existem 6 linhas com valores nulos na coluna “Subsetor”. Podemos identificá-las utilizando o método .isna():

ibov[ibov['Subsetor'].isna()]
Setor Código Ação Tipo Qtde. Teórica Part. (%) Part. (%)Acum. Subsetor
28 Diversos COGN3 COGNA ON ON NM 1814920980 0.269 3.056 None
29 Diversos RENT3 LOCALIZA ON NM 853202347 2.337 3.056 None
30 Diversos VAMO3 VAMOS ON NM 421383330 0.180 3.056 None
31 Diversos YDUQ3 YDUQS PART ON NM 289347914 0.270 3.056 None
73 Telecomunicação VIVT3 TELEF BRASIL ON 423091712 0.956 1.574 None
74 Telecomunicação TIMS3 TIM ON NM 807896814 0.618 1.574 None

Substituiremos os dados faltantes do Subsetor pelos mesmos do Setor.

ibov['Subsetor'] = ibov['Subsetor'].fillna(ibov['Setor'])
ibov.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 87 entries, 0 to 86
Data columns (total 8 columns):
 #   Column          Non-Null Count  Dtype  
---  ------          --------------  -----  
 0   Setor           87 non-null     object 
 1   Código          87 non-null     object 
 2   Ação            87 non-null     object 
 3   Tipo            87 non-null     object 
 4   Qtde. Teórica   87 non-null     int64  
 5   Part. (%)       87 non-null     float64
 6   Part. (%)Acum.  87 non-null     float64
 7   Subsetor        87 non-null     object 
dtypes: float64(2), int64(1), object(5)
memory usage: 5.6+ KB

Entendemos que alguns setores podem ser agrupados. Portanto, vamos consolidá-los para formar 10 setores distintos.

def agrupa_setor(setor):
  if setor in ['Cons N  Básico', 'Cons N Cíclico', 'Cons N Ciclico']: return "Consumo Não-Cíclico"
  if setor in ['Financ e Outros', 'Financeiro e Outros']: return "Financeiro"
  if setor in ['Tec.Informação', 'Telecomunicação']: return "TI e Telecom"
  else: return setor

ibov['Setor'] = ibov['Setor'].apply(agrupa_setor)
ibov['Setor'].unique()
array(['Bens Indls', 'Consumo Não-Cíclico', 'Consumo Cíclico', 'Diversos',
       'Financeiro', 'Mats Básicos', 'Petróleo', 'Saúde', 'TI e Telecom',
       'Utilidade Públ'], dtype=object)

Critérios de Seleção#

As ações escolhidas serão aquelas que ocupam a maior participação em cada setor. Para isso, ordenou-se as ações por participação na ordem decrescente e selecionou-se a primeira de cada setor.

# Ordenando as ações por participação na ordem decrescente e selecionando a primeira de cada setor.
Carteira = ibov.sort_values(by=['Part. (%)'], ascending=False).groupby(['Setor']).head(1)
Carteira
Setor Código Ação Tipo Qtde. Teórica Part. (%) Part. (%)Acum. Subsetor
51 Mats Básicos VALE3 VALE ON NM 4196924316 13.681 14.311 Mineração
60 Petróleo PETR4 PETROBRAS PN N2 4566445852 7.805 17.879 Gás e Biocombustíveis
40 Financeiro ITUB4 ITAUUNIBANCO PN N1 4801593832 7.189 17.747 Interms Financs
79 Utilidade Públ ELET3 ELETROBRAS ON N1 1980568384 3.843 10.954 Energ Elétrica
11 Consumo Não-Cíclico ABEV3 AMBEV S/A ON 4394245879 2.675 2.675 Bebidas
29 Diversos RENT3 LOCALIZA ON NM 853202347 2.337 3.056 Diversos
0 Bens Indls WEGE3 WEG ON NM 1481593024 2.316 2.316 Máqs e Equips
67 Saúde RADL3 RAIADROGASIL ON NM 1275798515 1.614 2.248 Comércio Distr.
73 TI e Telecom VIVT3 TELEF BRASIL ON 423091712 0.956 1.574 Telecomunicação
20 Consumo Cíclico LREN3 LOJAS RENNER ON NM 951329770 0.703 1.431 Comércio

Ponderação#

Determinou-se os pesos das ação pela sua participação no Ibovespa. Ao adotar essa abordagem de ponderação, buscou-se assegurar que a composição da carteira reflita não apenas a posição no setor, mas também a influência da ação no índice como um indicador mais abrangente do mercado.

#Distribuindo os pesos na carteira
Carteira['Peso'] = Carteira['Part. (%)'] / Carteira['Part. (%)'].sum()
#Selecionando somente as colunas necessárias.
Carteira = Carteira[['Código', 'Ação', 'Peso', 'Setor', 'Subsetor']].reset_index(drop=True)
Carteira
Código Ação Peso Setor Subsetor
0 VALE3 VALE 0.317285 Mats Básicos Mineração
1 PETR4 PETROBRAS 0.181011 Petróleo Gás e Biocombustíveis
2 ITUB4 ITAUUNIBANCO 0.166725 Financeiro Interms Financs
3 ELET3 ELETROBRAS 0.089125 Utilidade Públ Energ Elétrica
4 ABEV3 AMBEV S/A 0.062038 Consumo Não-Cíclico Bebidas
5 RENT3 LOCALIZA 0.054199 Diversos Diversos
6 WEGE3 WEG 0.053712 Bens Indls Máqs e Equips
7 RADL3 RAIADROGASIL 0.037431 Saúde Comércio Distr.
8 VIVT3 TELEF BRASIL 0.022171 TI e Telecom Telecomunicação
9 LREN3 LOJAS RENNER 0.016304 Consumo Cíclico Comércio
#Salvando os dados da carteira em formato .xlsx com pandas
Carteira.to_excel('./data/carteira.xlsx', index = False)

Conclusão#

Por fim, temos uma carteira que tenta refletir as principais posições nos setores do Ibovespa, com pesos proporcionais à participação de cada ação no índice e contando com informações sobre o setor, subsetor e nome da ação, os quais serão utilizadas em análises futuras.