Este JNB é similar a um estudo feito em um JNB anterior, onde trabalhamos com ações do IDIV:
https://rafsz.github.io/Python+Pandas21/Trading_IDIV_with_Pandas_20abr22_15h.html
Contudo, neste JNB, usamos o IFIX, e tivemos que trabalhar mais no Pandas para obter o resultado desejado, uma vez que muitos códigos não tinham cotação na base do Yahoo
Este JNB tem 3 etapas:
# importando as Bibliotecas que vamos usar
import yfinance as yf
import pandas as pd
# Neste JNB vamos usar uma lista de códigos de FIIs tirados do IFIX da B3
# FII = Fundo de Investimento Imobiliário - para saber mais:
# https://www.b3.com.br/pt_br/produtos-e-servicos/negociacao/renda-variavel/fundos-de-investimento-imobiliario-fii.htm
# O arq CSV baixado do site da B3 foi editado no LibreOffice e salvo como xlsx (Excel)
# https://www.b3.com.br/pt_br/market-data-e-indices/indices/indices-de-segmentos-e-setoriais/indice-fundos-de-investimentos-imobiliarios-ifix-composicao-da-carteira.htm
# Lendo o arq excel simplificado com 3 colunas com a composicao do IDIV e salvando num Pandas df
dftickers = pd.read_excel("IFIXDia_13-04-22.xlsx")
# visualizando o df
display (dftickers)
| Codigo | Acao | |
|---|---|---|
| 0 | BPFF11 | FII ABSOLUTO |
| 1 | AFHI11 | FII AFHI CRI |
| 2 | ALZR11 | FII ALIANZA |
| 3 | ARCT11 | FII ARCTIUM |
| 4 | AIEC11 | FII AUTONOMY |
| ... | ... | ... |
| 99 | XPLG11 | FII XP LOG |
| 100 | XPCM11 | FII XP MACAE |
| 101 | XPML11 | FII XP MALLS |
| 102 | XPPR11 | FII XP PROP |
| 103 | XPSF11 | FII XP SELEC |
104 rows × 2 columns
# Vamos inserir uma coluna com os codigos.SA para poder gerar a lista de códigos de maneira a poder pegar as cotações na base do Yahoo
# https://www.statology.org/pandas-combine-two-columns/
dftickers['codigoSA'] = dftickers['Codigo'] + '.SA'
display(dftickers)
| Codigo | Acao | codigoSA | |
|---|---|---|---|
| 0 | BPFF11 | FII ABSOLUTO | BPFF11.SA |
| 1 | AFHI11 | FII AFHI CRI | AFHI11.SA |
| 2 | ALZR11 | FII ALIANZA | ALZR11.SA |
| 3 | ARCT11 | FII ARCTIUM | ARCT11.SA |
| 4 | AIEC11 | FII AUTONOMY | AIEC11.SA |
| ... | ... | ... | ... |
| 99 | XPLG11 | FII XP LOG | XPLG11.SA |
| 100 | XPCM11 | FII XP MACAE | XPCM11.SA |
| 101 | XPML11 | FII XP MALLS | XPML11.SA |
| 102 | XPPR11 | FII XP PROP | XPPR11.SA |
| 103 | XPSF11 | FII XP SELEC | XPSF11.SA |
104 rows × 3 columns
# Temos 104 FIIS neste df
# Transformando a Coluna de Tickers (códigos de negociação) em uma Lista de Tickers
# https://www.delftstack.com/howto/python-pandas/pandas-column-to-list/
ltickers104 = dftickers['codigoSA'].tolist()
print(ltickers104)
print(len(ltickers104))
['BPFF11.SA', 'AFHI11.SA', 'ALZR11.SA', 'ARCT11.SA', 'AIEC11.SA', 'BARI11.SA', 'BBPO11.SA', 'BCFF11.SA', 'BRCR11.SA', 'BCIA11.SA', 'BCRI11.SA', 'BLMR11.SA', 'BLMG11.SA', 'BRCO11.SA', 'BZLI11.SA', 'BTAL11.SA', 'BTCR11.SA', 'BTLG11.SA', 'CPFF11.SA', 'CPTS11.SA', 'HGCR11.SA', 'HGFF11.SA', 'HGLG11.SA', 'HGRU11.SA', 'DEVA11.SA', 'FEXC11.SA', 'VRTA11.SA', 'GTWR11.SA', 'FIGS11.SA', 'GGRC11.SA', 'GALG11.SA', 'HABT11.SA', 'HCTR11.SA', 'HGBS11.SA', 'HGRE11.SA', 'HSAF11.SA', 'HSLG11.SA', 'HSML11.SA', 'HFOF11.SA', 'FIIB11.SA', 'IRDM11.SA', 'JSRE11.SA', 'KISU11.SA', 'KNRI11.SA', 'KNHY11.SA', 'KNIP11.SA', 'KNCR11.SA', 'KNSC11.SA', 'KFOF11.SA', 'MALL11.SA', 'MCCI11.SA', 'MXRF11.SA', 'MFII11.SA', 'MGFF11.SA', 'MORE11.SA', 'NCHB11.SA', 'OUJP11.SA', 'PATL11.SA', 'PLCR11.SA', 'PORD11.SA', 'QAGR11.SA', 'RBRL11.SA', 'RBRY11.SA', 'RBRP11.SA', 'RBRF11.SA', 'RBRR11.SA', 'RECR11.SA', 'RECT11.SA', 'RBFF11.SA', 'RCRB11.SA', 'RBVA11.SA', 'RZAK11.SA', 'RZTR11.SA', 'SADI11.SA', 'SARE11.SA', 'SDIL11.SA', 'SPTW11.SA', 'SNFF11.SA', 'TEPP11.SA', 'TGAR11.SA', 'TORD11.SA', 'TRXF11.SA', 'URPR11.SA', 'VGHF11.SA', 'VGIP11.SA', 'VGIR11.SA', 'CVBI11.SA', 'LVBI11.SA', 'PVBI11.SA', 'RVBI11.SA', 'VCJR11.SA', 'VSLH11.SA', 'VIFI11.SA', 'VILG11.SA', 'VINO11.SA', 'VISC11.SA', 'VTLT11.SA', 'XPCI11.SA', 'XPIN11.SA', 'XPLG11.SA', 'XPCM11.SA', 'XPML11.SA', 'XPPR11.SA', 'XPSF11.SA'] 104
# No estudo com o IDIV usamos a combinação MM09x21 por ser uma combinação clássica para ações no gráfico diário, mas para FIIs eu percebi que esta combinação dá muitos "falsos-cruzamentos.
# Decidi usar, então a combinação MM09x80 - o que implica em pegar um período maior de cotações para poder calcular a MM80
# Pegando as cotações de Fechamento Ajustado e guardando num df (cotações = quotes em inglês)
# Queremos cotações até 6a-feira 15/04/22, então a data final vai ser o dia seguinte
dfquotes104 = yf.download(ltickers104, start="2021-11-01", end = "2022-04-16")["Adj Close"]
dfquotes104.shape
[*********************100%***********************] 104 of 104 completed 24 Failed downloads: - KISU11.SA: No data found for this date range, symbol may be delisted - TORD11.SA: No data found for this date range, symbol may be delisted - FEXC11.SA: No data found for this date range, symbol may be delisted - VGHF11.SA: No data found for this date range, symbol may be delisted - PATL11.SA: No data found for this date range, symbol may be delisted - RZTR11.SA: No data found for this date range, symbol may be delisted - NCHB11.SA: No data found for this date range, symbol may be delisted - BTAL11.SA: No data found for this date range, symbol may be delisted - RZAK11.SA: No data found for this date range, symbol may be delisted - HSLG11.SA: No data found for this date range, symbol may be delisted - BLMR11.SA: No data found for this date range, symbol may be delisted - ARCT11.SA: No data found for this date range, symbol may be delisted - BCFF11.SA: No data found for this date range, symbol may be delisted - DEVA11.SA: No data found for this date range, symbol may be delisted - CPTS11.SA: No data found for this date range, symbol may be delisted - BLMG11.SA: No data found for this date range, symbol may be delisted - SNFF11.SA: No data found for this date range, symbol may be delisted - HSAF11.SA: No data found for this date range, symbol may be delisted - TGAR11.SA: No data found for this date range, symbol may be delisted - AFHI11.SA: No data found for this date range, symbol may be delisted - GALG11.SA: No data found for this date range, symbol may be delisted - VSLH11.SA: No data found for this date range, symbol may be delisted - URPR11.SA: No data found for this date range, symbol may be delisted - PVBI11.SA: No data found for this date range, symbol may be delisted
(113, 104)
# Para confirmar se apenas temos 24 colunas totalmente nulas, vamos criar um df com a soma de nulos por papel(coluna do dfquotes104) e depois filtrar as colunas todas nulas:
dfisna = dfquotes104.isna().sum()
display(dfisna)
AFHI11.SA 113
AIEC11.SA 90
ALZR11.SA 0
ARCT11.SA 113
BARI11.SA 0
...
XPIN11.SA 0
XPLG11.SA 0
XPML11.SA 0
XPPR11.SA 0
XPSF11.SA 0
Length: 104, dtype: int64
# O df acima tem 104 papeis, e alguns deles têm 113 valores nulos
# Como o dfquotes104 têm 113 linhas, isto significa que os papeís com 113 valores nulos na verdade são colunas do dfquotes104 que estão todas nulas
# Vamos trabalhar o dfisna um pouco para confirmar quantas colunas têm 113 valores nulos
# Incluindo index
dfisna = dfisna.reset_index()
# E renomenado as colunas
dfisna.columns = ['papel', 'isnasum']
display(dfisna)
| papel | isnasum | |
|---|---|---|
| 0 | AFHI11.SA | 113 |
| 1 | AIEC11.SA | 90 |
| 2 | ALZR11.SA | 0 |
| 3 | ARCT11.SA | 113 |
| 4 | BARI11.SA | 0 |
| ... | ... | ... |
| 99 | XPIN11.SA | 0 |
| 100 | XPLG11.SA | 0 |
| 101 | XPML11.SA | 0 |
| 102 | XPPR11.SA | 0 |
| 103 | XPSF11.SA | 0 |
104 rows × 2 columns
# criando um outro df que vai ter apenas as colunas nulas
dfisnab = dfisna[dfisna['isnasum'] == 113]
display(dfisnab)
dfisnab.shape
| papel | isnasum | |
|---|---|---|
| 0 | AFHI11.SA | 113 |
| 3 | ARCT11.SA | 113 |
| 6 | BCFF11.SA | 113 |
| 9 | BLMG11.SA | 113 |
| 10 | BLMR11.SA | 113 |
| 14 | BTAL11.SA | 113 |
| 19 | CPTS11.SA | 113 |
| 21 | DEVA11.SA | 113 |
| 22 | FEXC11.SA | 113 |
| 25 | GALG11.SA | 113 |
| 37 | HSAF11.SA | 113 |
| 38 | HSLG11.SA | 113 |
| 43 | KISU11.SA | 113 |
| 56 | NCHB11.SA | 113 |
| 58 | PATL11.SA | 113 |
| 61 | PVBI11.SA | 113 |
| 74 | RZAK11.SA | 113 |
| 75 | RZTR11.SA | 113 |
| 79 | SNFF11.SA | 113 |
| 82 | TGAR11.SA | 113 |
| 83 | TORD11.SA | 113 |
| 85 | URPR11.SA | 113 |
| 87 | VGHF11.SA | 113 |
| 95 | VSLH11.SA | 113 |
(24, 2)
# Com o dfisnab confirmamos 113 papeis com valores nulos
# Vamos criar uma cópia deste df104 para remover estas colunas e preservar o histórico para consulta futura, se necessário
dfquotes80 = dfquotes104.copy()
# Removendo as colunas que estão completamente nulas
dfquotes80 = dfquotes80.dropna(axis = 1, how ='all')
# Devemos ter então um df com 113 linhas (dias) e 80 colunas (FIIs)
dfquotes80.shape
(113, 80)
# Vamos calcular as Médias Móveis (MM) e substituir os valores das cotações pela diferença da MM09 para a MM80
# Esta diferença vai nos indicar se a MM09 está acima (valor positivo) da MM80 ou abaixo (valor negativo)
dfMM80 = dfquotes80.copy()
for (col) in dfMM80:
dfMM80[col] = (dfMM80[col].rolling(9).mean()) - (dfMM80[col].rolling(80).mean())
# Como ainda temos alguns papéis com histórico curto, vamos seguir limpando o df
# Primeiro vamos excluir as linhas que estão todas nulas
dfMM80 = dfMM80.dropna(axis = 0, how ='all')
dfMM80.shape
(34, 80)
# Então seguimos com 80 FIIs mas agora só temos 34 linhas (antes eram 113) - o q faz sentido, pois a MM80 não pode ser calculada para as primeiras 79 linhas
# Vamos ver quantos papéis ainda têm null no seu histórico:
dfisnac = dfMM80.isna().sum()
display (dfisnac)
AIEC11.SA 34
ALZR11.SA 0
BARI11.SA 0
BBPO11.SA 0
BCIA11.SA 0
..
XPIN11.SA 0
XPLG11.SA 0
XPML11.SA 0
XPPR11.SA 0
XPSF11.SA 0
Length: 80, dtype: int64
dfisnac = dfisnac.reset_index()
dfisnac.columns = ['papel', 'isnasum']
display(dfisnac)
| papel | isnasum | |
|---|---|---|
| 0 | AIEC11.SA | 34 |
| 1 | ALZR11.SA | 0 |
| 2 | BARI11.SA | 0 |
| 3 | BBPO11.SA | 0 |
| 4 | BCIA11.SA | 0 |
| ... | ... | ... |
| 75 | XPIN11.SA | 0 |
| 76 | XPLG11.SA | 0 |
| 77 | XPML11.SA | 0 |
| 78 | XPPR11.SA | 0 |
| 79 | XPSF11.SA | 0 |
80 rows × 2 columns
# https://www.geeksforgeeks.org/selecting-rows-in-pandas-dataframe-based-on-conditions/
# Vamos filtrar quais papéis têm valores nulos
dfisnad = dfisnac[dfisnac['isnasum'] != 0]
display(dfisnad)
| papel | isnasum | |
|---|---|---|
| 0 | AIEC11.SA | 34 |
| 11 | BZLI11.SA | 34 |
| 35 | KNSC11.SA | 34 |
| 41 | MORE11.SA | 34 |
| 49 | RBRL11.SA | 34 |
# Então temos outras 5 colunas nulas, já que o dfMM80 tem 34 linhas
# Vamos dropar estas colunas
dfMM75 = dfMM80.copy()
dfMM75 = dfMM75.dropna(axis = 1, how ='all')
dfMM75.shape
(34, 75)
# Vamos criar uma funcao para obter as mensagens
def alertasimples (acao):
if (dfMM75.at[dfMM75.index[a],acao] > 0) and (dfMM75.at[dfMM75.index[b],acao] < 0):
return('BUY')
elif (dfMM75.at[dfMM75.index[a],acao] < 0) and (dfMM75.at[dfMM75.index[b],acao] > 0):
return('STOP!')
elif (dfMM75.at[dfMM75.index[a],acao] > 0) and (dfMM75.at[dfMM75.index[b],acao] > 0):
return('hold')
else:
return('wait')
ltickers75 = dfMM75.columns.tolist()
print(len(ltickers75))
75
# Gerando um df a partir da lista de 75 FIIs, para depois preencher com as mensagens
dfalert75 = pd.DataFrame(ltickers75)
display(dfalert75)
| 0 | |
|---|---|
| 0 | ALZR11.SA |
| 1 | BARI11.SA |
| 2 | BBPO11.SA |
| 3 | BCIA11.SA |
| 4 | BCRI11.SA |
| ... | ... |
| 70 | XPIN11.SA |
| 71 | XPLG11.SA |
| 72 | XPML11.SA |
| 73 | XPPR11.SA |
| 74 | XPSF11.SA |
75 rows × 1 columns
# Renomeando a coluna do df
dfalert75.columns = ['FII']
display(dfalert75)
| FII | |
|---|---|
| 0 | ALZR11.SA |
| 1 | BARI11.SA |
| 2 | BBPO11.SA |
| 3 | BCIA11.SA |
| 4 | BCRI11.SA |
| ... | ... |
| 70 | XPIN11.SA |
| 71 | XPLG11.SA |
| 72 | XPML11.SA |
| 73 | XPPR11.SA |
| 74 | XPSF11.SA |
75 rows × 1 columns
# Obtendo as mensagens e salvando em um novo df
# O range de a usado na funcao alerta deve ser igual ao número de dias do dfMM75
for a in range (34):
dctemp = {}
for papel in ltickers75:
b=a-1
dctemp[papel]=alertasimples(papel)
dfalert75[dfMM75.index[a]] = dctemp.values()
display(dfalert75)
| FII | 2022-02-24 00:00:00 | 2022-02-25 00:00:00 | 2022-03-02 00:00:00 | 2022-03-03 00:00:00 | 2022-03-04 00:00:00 | 2022-03-07 00:00:00 | 2022-03-08 00:00:00 | 2022-03-09 00:00:00 | 2022-03-10 00:00:00 | ... | 2022-04-01 00:00:00 | 2022-04-04 00:00:00 | 2022-04-05 00:00:00 | 2022-04-06 00:00:00 | 2022-04-07 00:00:00 | 2022-04-08 00:00:00 | 2022-04-11 00:00:00 | 2022-04-12 00:00:00 | 2022-04-13 00:00:00 | 2022-04-14 00:00:00 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | ALZR11.SA | hold | hold | hold | hold | hold | hold | hold | STOP! | wait | ... | BUY | hold | hold | hold | hold | hold | hold | hold | hold | hold |
| 1 | BARI11.SA | BUY | hold | hold | hold | hold | hold | hold | hold | hold | ... | wait | wait | wait | wait | wait | wait | wait | wait | wait | wait |
| 2 | BBPO11.SA | BUY | hold | hold | hold | STOP! | wait | wait | wait | wait | ... | wait | wait | wait | wait | wait | wait | wait | wait | wait | wait |
| 3 | BCIA11.SA | BUY | STOP! | wait | wait | wait | wait | wait | wait | wait | ... | wait | wait | wait | wait | wait | wait | wait | wait | wait | wait |
| 4 | BCRI11.SA | BUY | hold | hold | hold | hold | hold | hold | hold | hold | ... | hold | hold | hold | hold | hold | hold | STOP! | wait | wait | wait |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 70 | XPIN11.SA | wait | wait | wait | wait | wait | wait | wait | wait | wait | ... | wait | wait | wait | wait | wait | wait | wait | wait | wait | wait |
| 71 | XPLG11.SA | wait | wait | wait | wait | wait | wait | wait | wait | BUY | ... | hold | hold | STOP! | wait | wait | wait | wait | wait | wait | wait |
| 72 | XPML11.SA | STOP! | wait | wait | wait | wait | wait | wait | wait | wait | ... | BUY | hold | hold | hold | hold | hold | hold | hold | hold | hold |
| 73 | XPPR11.SA | wait | wait | wait | wait | wait | wait | wait | wait | wait | ... | wait | wait | wait | wait | wait | wait | wait | wait | wait | wait |
| 74 | XPSF11.SA | wait | wait | wait | wait | wait | wait | wait | wait | wait | ... | wait | wait | wait | wait | wait | wait | wait | wait | wait | wait |
75 rows × 35 columns
# Salvando em XLSX:
writer = pd.ExcelWriter('IFIX_df_alert75_.xlsx', engine='xlsxwriter')
dfalert75.to_excel(writer, sheet_name='Mensagens')
writer.save()
Canal Youtube: Código Quant - Finanças Quantitativas
PYTHON PARA INVESTIMENTOS #2: Definindo intervalos, calculando e plotando médias móveis
https://youtu.be/BBomKv3NFNc?list=PLmQ5Q79miLmxDc16motuYuG1hLjW0T4Wo
Inserir uma nova coluna num df e copiar os valores de um dicionario pra esta coluna, contanto que as chaves do dicionario batam com alguma coluna do df: https://www.geeksforgeeks.org/use-get-method-to-create-a-dictionary-in-python-from-a-list-of-elements/?ref=gcse
Meu repositório GitHub onde está o JNB que gerou este html:
https://github.com/Rafsz/rafsz.github.io