debentures-dot-com 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ class UrlDebentures:
2
+
3
+ def __init__(self):
4
+ self.root_url = 'https://www.debentures.com.br/exploreosnd/consultaadados'
@@ -0,0 +1,5 @@
1
+ # src/debentures_dot_com/__init__.py
2
+ from .emissoes import EmissoesDebentures
3
+ from .estoques import EstoquesCorporativos
4
+ from .eventos_fin import EventosFinanceiros
5
+ from .mercados import MercadoSecundario
@@ -0,0 +1,9 @@
1
+
2
+ volume/volumeporperiodo_e.asp?op_exc=False&emissao=0&dt_ini=01%2F07%2F2025&dt_fim=23%2F07%2F2025&ICVM=&moeda=1
3
+ volume/volumeporperiodo_e.asp?op_exc=Nada&emissao=2&dt_ini=01%2F07%2F2024&dt_fim=23%2F07%2F2025&ICVM=&moeda=1
4
+
5
+ /volume/volumeporgarantia-especie_d.asp?pMes_Ini=05&pAno_Ini=2025&pMes_Fim=07&pAno_Fim=2025&moeda=1&pConsol=1&op_exc=Nada&ICVM=%20NULL
6
+ volume/volumeporgarantia-especie_d.asp?pMes_Ini=5&pAno_Ini=2025&pMes_Fim=5&pAno_Fim=2025&pConsol=0&moeda=1&op_exc=Nada&ICVM=%20NULL
7
+ volume/volumeporgarantia-especie_de.asp?pMes_Ini=05&pAno_Ini=2025&pMes_Fim=07&pAno_Fim=2025&moeda=1&pConsol=1&op_exc=Nada&ICVM
8
+
9
+ https://www.debentures.com.br/exploreosnd/consultaadados/volume/volumeporgarantia-especie_de.asp?pMes_Ini=05&pAno_Ini=2025&pMes_Fim=07&pAno_Fim=2025&moeda=1&pConsol=1&op_exc=Nada&ICVM=%20NULL&ICVM=NULL
@@ -0,0 +1,175 @@
1
+ import requests
2
+ import io
3
+ import pandas as pd
4
+ from bs4 import BeautifulSoup
5
+ from datetime import date
6
+ from .utils.utils import get_response_to_pd, _format_cnpj, _format_date_for_url
7
+ from .__consulta_dados import UrlDebentures
8
+
9
+ class EmissoesDebentures:
10
+ def __init__(self)->str:
11
+ root_url = UrlDebentures().root_url
12
+ self.root_url = f'{root_url}/emissoesdedebentures'
13
+
14
+ def lista_deb_publicas(self,timeout:int=None)->pd.DataFrame:
15
+ url = f'{self.root_url}/caracteristicas_r.asp?tip_deb=publicas&op_exc='
16
+ timeout = timeout if isinstance(timeout,int) else 10
17
+ r = requests.get(url,timeout=timeout)
18
+ soup = BeautifulSoup(r.text, 'html.parser')
19
+ table = soup.find('table', class_='Tab10333333')
20
+ # Check if the table exists
21
+ if table:
22
+ df = []
23
+ # Extract table rows
24
+ rows = table.find_all('tr')
25
+ # Loop through rows and extract data
26
+ for row in rows:
27
+ cells = row.find_all(['td', 'th']) # handles both header and data cells
28
+ cell_texts = [cell.get_text(strip=True) for cell in cells]
29
+ df.append(cell_texts[1:-1])
30
+ df = pd.DataFrame(df, columns = ['Ativo', 'Emissor', 'Dump', 'Situacao'])
31
+ df = df.drop(columns=['Dump'])
32
+ else:
33
+ df = pd.DataFrame()
34
+ print("Table with class 'Tab10333333' not found.")
35
+ return df
36
+
37
+ def lista_caracteristicas(self, ativo:str,timeout:int=None)->pd.DataFrame:
38
+ url = f'{self.root_url}/caracteristicas_e.asp?Ativo={ativo}'
39
+ timeout = timeout if isinstance(timeout,int) else 10
40
+ r = requests.get(url,timeout=timeout)
41
+ df = pd.read_csv(io.StringIO(r.text), sep='|', encoding='utf-8', names=['raw'], skiprows=2)
42
+ df = df[1:]['raw'].str.split('\t', expand=True).reset_index(drop=True)
43
+ df = df.T.reset_index(drop=True)
44
+ df.columns = ['Descricao', 'Valores']
45
+ return df
46
+
47
+ #def _dt_fim_ini_fix(self, date_):
48
+ # dt_par = parser.parse(date_)
49
+ # return f'{dt_par.day:02d}%2F{dt_par.month:02d}%2F{dt_par.year}'
50
+
51
+ def pu_historico(self, ativo:str, dt_inicio:str=None, dt_fim:str=None,timeout:int=None)->pd.DataFrame:
52
+ params = []
53
+
54
+ if not dt_inicio:
55
+ dt_inicio= '20010101'
56
+
57
+ if not dt_fim:
58
+ dt_fim = date.today().strftime('%Y%m%d')
59
+
60
+ dt_inicio_fmt = _format_date_for_url(dt_inicio)
61
+ params.append(f'dt_ini={dt_inicio_fmt}')
62
+ dt_fim_fmt = _format_date_for_url(dt_fim)
63
+ params.append(f'dt_fim={dt_fim_fmt}')
64
+
65
+ # Add conditional suffix if any date filters exist
66
+ add_suffix = '++++' if params else ''
67
+
68
+ query_string = '&' + '&'.join(params) if params else ''
69
+ url = f'{self.root_url}/puhistorico_e.asp?op_exc=False&ativo={ativo}{add_suffix}{query_string}'
70
+
71
+ return get_response_to_pd(url,timeout=timeout)
72
+
73
+ def prazo_medio(self, ativo:str = None, emissor:str = None, datacvm:str = None, dt_ini:str=None, dt_fim:str=None, anoini:str=None, anofim:str=None, repactuacao:str = None, exec:str = None,timeout:int=None)->list:
74
+ ativo = ativo if isinstance(ativo, str) else ''
75
+ emissor = _format_cnpj(emissor) if emissor else ''
76
+ datacvm = datacvm if isinstance(datacvm, str) else 'e'
77
+ dt_ini = _format_date_for_url(dt_ini)
78
+ dt_fim = _format_date_for_url(dt_fim)
79
+ repactuacao = repactuacao if isinstance(repactuacao, str) else ''
80
+ exec = exec if isinstance(exec, str) else 'Nada'
81
+ if isinstance(anoini, int):
82
+ anoini = str(anoini)
83
+ elif not isinstance(anoini, str):
84
+ anoini = ''
85
+
86
+ if isinstance(anofim, int):
87
+ anofim = str(anofim)
88
+ elif not isinstance(anofim, str):
89
+ anofim = ''
90
+ url = (
91
+ f'{self.root_url}/prazo-medio_e.asp?'
92
+ f'Ativo={ativo}&Emissor={emissor}&dataCVM={datacvm}&dt_ini={dt_ini}'
93
+ f'&dt_fim={dt_fim}&anoini={anoini}&anofim={anofim}&ComRepactuacao={repactuacao}&Op_exc={exec}'
94
+ )
95
+ df = get_response_to_pd(url,timeout)
96
+ df_medio = df[:2]
97
+ df_medio = df_medio.iloc[:, :3]
98
+ df_medio.columns = ['', 'Tipo Prazo', 'Prazo Medio']
99
+ df_medio= df_medio.drop(columns='')
100
+ df_data = df[2:].reset_index(drop=True)
101
+ df_data.columns = df_data.iloc[0]
102
+ df_data = df_data[1:].reset_index(drop=True)[:-2]
103
+ return df_medio, df_data
104
+
105
+ def conversao_permuta(self, ativo:str=None, exec:bool=None, dt_ini:str=None, dt_fim:str=None, classe:str=None,timeout:int=None)->pd.DataFrame:
106
+ ativo = ativo if isinstance(ativo, str) else ''
107
+ dt_ini = _format_date_for_url(dt_ini)
108
+ dt_fim = _format_date_for_url(dt_fim)
109
+ classe = classe if isinstance(classe, str) else ''
110
+ exec = exec if isinstance(exec, bool) else False
111
+ url = (
112
+ f'{self.root_url}/conversoes-permutas_e.asp?'
113
+ f'ativo={ativo},%20&op_exc={exec}&dt_ini={dt_ini}&dt_fim={dt_fim}&classe={classe}'
114
+ )
115
+ return get_response_to_pd(url,timeout=timeout)
116
+
117
+ def caracteristicas_debs(self, tipo:str=None,exec:bool=None,mnome:str=None,ativo:str=None,
118
+ ipo:str=None,icvm:str=None,escri_padro:str=None,cvm_ini:str=None,cvm_fim:str=None,
119
+ emis_ini:str=None,emis_fim:str=None,venc_ini:str=None,venc_fim:str=None,tpv:str=None,
120
+ tnv:str=None,rent_ini:str=None,rent_fim:str=None,distrib_ini:str=None,distrib_fim:str=None,
121
+ indice:str=None,tipo_:str=None,crit_calc:str=None,dia_ref:str=None,mult_rend:str=None,
122
+ limite:str=None,trat_limite:str=None,tx_spread:str=None,prazo:str=None,premio_novo:str=None,
123
+ premio_prazo:str=None,premio_antigo:str=None,par:str=None,amortizacao:str=None,mbanco:str=None,
124
+ magente:str=None,instdep:str=None,coordenador:str=None)->pd.DataFrame:
125
+ tipo = tipo if isinstance(tipo, str) else 'privadas'
126
+ exec = exec if isinstance(exec, bool) else False
127
+ mnome = mnome if isinstance(mnome, str) else ''
128
+ ativo = ativo if isinstance(ativo, str) else ''
129
+ ipo = ipo if isinstance(ipo, str) else ''
130
+ icvm = icvm if isinstance(icvm, str) else ''
131
+ escri_padro = escri_padro if isinstance(escri_padro, str) else ''
132
+ cvm_ini = cvm_ini if isinstance(cvm_ini, str) else ''
133
+ cvm_fim = cvm_fim if isinstance(cvm_fim, str) else ''
134
+ emis_ini = emis_ini if isinstance(emis_ini, str) else ''
135
+ emis_fim = emis_fim if isinstance(emis_fim, str) else ''
136
+ venc_ini = venc_ini if isinstance(venc_ini, str) else ''
137
+ venc_fim = venc_fim if isinstance(venc_fim, str) else ''
138
+ tpv = tpv if isinstance(tpv, str) else ''
139
+ tnv = tnv if isinstance(tnv, str) else ''
140
+ rent_ini = rent_ini if isinstance(rent_ini, str) else ''
141
+ rent_fim = rent_fim if isinstance(rent_fim, str) else ''
142
+ distrib_ini = distrib_ini if isinstance(distrib_ini, str) else ''
143
+ distrib_fim = distrib_fim if isinstance(distrib_fim, str) else ''
144
+ indice = indice if isinstance(indice, str) else ''
145
+ tipo_ = tipo_ if isinstance(tipo_, str) else ''
146
+ crit_calc = crit_calc if isinstance(crit_calc, str) else ''
147
+ dia_ref = dia_ref if isinstance(dia_ref, str) else ''
148
+ mult_rend = mult_rend if isinstance(mult_rend, str) else ''
149
+ limite = limite if isinstance(limite, str) else ''
150
+ trat_limite = trat_limite if isinstance(trat_limite, str) else ''
151
+ tx_spread = tx_spread if isinstance(tx_spread, str) else ''
152
+ prazo = prazo if isinstance(prazo, str) else ''
153
+ premio_novo = premio_novo if isinstance(premio_novo, str) else ''
154
+ premio_prazo = premio_prazo if isinstance(premio_prazo, str) else ''
155
+ premio_antigo = premio_antigo if isinstance(premio_antigo, str) else ''
156
+ par = par if isinstance(par, str) else ''
157
+ amortizacao = amortizacao if isinstance(amortizacao, str) else ''
158
+ mbanco = mbanco if isinstance(mbanco, str) else ''
159
+ magente = magente if isinstance(magente, str) else ''
160
+ instdep = instdep if isinstance(instdep, str) else ''
161
+ coordenador = coordenador if isinstance(coordenador, str) else ''
162
+
163
+ url = (
164
+ f'{self.root_url}/caracteristicas_e.asp?tip_deb={tipo}&'
165
+ f'op_exc={exec}&mnome={mnome}&ativo={ativo}&IPO={ipo}&icvm={icvm}&EscrituraPadronizada={escri_padro}&'
166
+ f'cvm_ini={cvm_ini}&cvm_fim={cvm_fim}&emis_ini={emis_ini}&emis_fim={emis_fim}&venc_ini={venc_ini}&venc_fim={venc_fim}&TPV={tpv}&'
167
+ f'TNV={tnv}&rent_ini={rent_ini}&rent_fim={rent_fim}&distrib_ini={distrib_ini}&distrib_fim={distrib_fim}&indice={indice}&'
168
+ f'tipo={tipo_}&crit_calc={crit_calc}&dia_ref={dia_ref}&mult_rend={mult_rend}&limite={limite}&trat_limite={trat_limite}&'
169
+ f'tx_spread={tx_spread}&prazo={prazo}&premio_novo={premio_novo}&premio_prazo={premio_prazo}&premio_antigo={premio_antigo}&Par={par}&'
170
+ f'amortizacao={amortizacao}&mbanco={mbanco}&magente={magente}&instdep={instdep}&coordenador={coordenador}'
171
+ )
172
+ df = get_response_to_pd(url)
173
+ df.columns = df.iloc[0]
174
+ df = df[1:].reset_index(drop=True)[:-2]
175
+ return df
@@ -0,0 +1,177 @@
1
+ import re
2
+ import pandas as pd
3
+ import requests
4
+ from dateutil import parser
5
+ from .utils.utils import get_response_to_pd, _format_cnpj, _format_date_for_url,simple_response_to_pd,get_soup_response_to_pd
6
+ from .__consulta_dados import UrlDebentures
7
+
8
+ def parse_estoque_data(data_string: str, tipo: str) -> pd.DataFrame:
9
+ """
10
+ Parses the given string content into a pandas DataFrame,
11
+ now also extracting the 'Moeda' type.
12
+
13
+ Args:
14
+ data_string: A string containing the stock data with multiple date blocks.
15
+
16
+ Returns:
17
+ A pandas DataFrame with the extracted stock information.
18
+ """
19
+ lines = data_string.strip().split('\n')
20
+ parsed_data = []
21
+ current_date = None
22
+ current_moeda = None # New variable to store the currency type
23
+
24
+ for line in lines:
25
+ line = line.strip()
26
+
27
+ # Skip header lines that are not part of the data blocks
28
+ if line.startswith("Estoque SND Caracter"):
29
+ continue
30
+
31
+ # Extract 'Data do Estoque' and 'Moeda'
32
+ # Adjusted regex to capture the currency symbol (R$ or US$)
33
+ date_moeda_match = re.match(r'Data do Estoque (\d{2}/\d{2}/\d{4}) - Moeda (R\$|US\$)', line)
34
+ if date_moeda_match:
35
+ current_date = pd.to_datetime(date_moeda_match.group(1), format='%d/%m/%Y')
36
+ current_moeda = date_moeda_match.group(2) # Capture the currency type
37
+ continue
38
+
39
+ # Identify header row for data blocks
40
+ if current_date and line.startswith(tipo):
41
+ headers = [h.strip() for h in line.split('\t') if h.strip()]
42
+ continue # Move to the next line to read data
43
+
44
+ # Read data rows (excluding "Total do dia")
45
+ if current_date and not line.startswith("Total do dia") and not line.startswith(tipo) and line:
46
+ parts = [p.strip() for p in line.split('\t') if p.strip()]
47
+ if parts and len(parts) == len(headers):
48
+ row_dict = {'Data do Estoque': current_date, 'Moeda': current_moeda} # Add 'Moeda' to the dictionary
49
+ for i, header in enumerate(headers):
50
+ value = parts[i]
51
+ # Clean and convert numeric values
52
+ if header != tipo:
53
+ value = value.replace('.', '').replace(',', '.') # Remove thousands separator, change decimal comma to dot
54
+ try:
55
+ value = float(value)
56
+ except ValueError:
57
+ pass # Keep as string if not a number
58
+ row_dict[header] = value
59
+ parsed_data.append(row_dict)
60
+
61
+ if not parsed_data:
62
+ return pd.DataFrame()
63
+
64
+ df = pd.DataFrame(parsed_data)
65
+
66
+ # Ensure correct column order and data types
67
+ if not df.empty:
68
+ df['Data do Estoque'] = pd.to_datetime(df['Data do Estoque'])
69
+ # Convert numeric columns, handling potential errors if any non-numeric data slipped through
70
+ for col in ['Mercado', 'Tesouraria', 'Total']:
71
+ if col in df.columns:
72
+ df[col] = pd.to_numeric(df[col], errors='coerce')
73
+ return df
74
+
75
+ def get_estoque_to_pd(url:str, tipo: str, timeout:int=None)-> pd.DataFrame:
76
+ timeout = timeout if isinstance(timeout, int) else 10
77
+ try:
78
+ response = requests.get(url, timeout=timeout)
79
+ response.encoding = 'ISO-8859-1'
80
+ response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
81
+ return parse_estoque_data(response.text, tipo)
82
+ except requests.exceptions.ConnectionError as e:
83
+ print(f"Connection Error for {url}: {e}")
84
+ return pd.DataFrame()
85
+ except requests.exceptions.Timeout as e:
86
+ print(f"Timeout Error for {url}: {e}")
87
+ return pd.DataFrame()
88
+ except requests.exceptions.HTTPError as e:
89
+ print(f"HTTP Error for {url}: {e}")
90
+ return pd.DataFrame()
91
+ except Exception as e:
92
+ print(f"An unexpected error occurred for {url}: {e}")
93
+ return pd.DataFrame()
94
+
95
+ def _consulta_relatorio(url:str, tipo_relatorio:str, dt_ini:str=None, dt_fim:str=None, moeda:int=None,opcao:int=None,exec:str=None,timeout:int=None)->pd.DataFrame:
96
+ dt_ini = _format_date_for_url(dt_ini, '%d/%m/%Y') if dt_ini else '02/03/1992'
97
+ dt_fim = _format_date_for_url(dt_fim,'%d/%m/%Y') if dt_fim else '31/12/2029'
98
+ if moeda is not None and moeda not in [1, 2]:
99
+ raise ValueError("Parameter 'moeda' must be 1 or 2.")
100
+ moeda = moeda if isinstance(moeda, int) else 1
101
+ exec = exec if isinstance(exec, str) else 'on'
102
+ opcao = opcao if isinstance(opcao, int) else 100
103
+ url = (
104
+ f'{url}/estoquepor_re.asp?op_rel={tipo_relatorio}'
105
+ f'&Dt_ini={dt_ini}&Dt_fim={dt_fim}&op_exc={exec}&op_subInd=&Opcao={opcao}&Moeda={moeda}'
106
+ )
107
+ return get_estoque_to_pd(url, tipo_relatorio, 1000)
108
+
109
+ class EstoquesCorporativos:
110
+ """
111
+ Consult the open data page on debentures.com,
112
+ specifically the tab 'estoque' (https://www.debentures.com.br/exploreosnd/consultaadados/estoque/)
113
+ to extract the information via Python.
114
+ """
115
+
116
+ def __init__(self)->str:
117
+ root_url = UrlDebentures().root_url
118
+ self.root_url = f'{root_url}/estoque'
119
+
120
+ def estoque_por_ativo(self, ativo:str=None, dt_ini:str=None, dt_fim:str=None, exec:str=None,moeda:int=None,timeout:int=None)->pd.DataFrame:
121
+ ativo = ativo if isinstance(ativo, str) else ''
122
+ exec = exec if isinstance(exec, str) else 'Nada'
123
+ if moeda is not None and moeda not in [1, 2]:
124
+ raise ValueError("Parameter 'moeda' must be 1 or 2.")
125
+ moeda = moeda if isinstance(moeda, int) else 1
126
+ dt_ini = _format_date_for_url(dt_ini, '%d/%m/%Y') if dt_ini else '02/03/1992'
127
+ dt_fim = _format_date_for_url(dt_fim, '%d/%m/%Y') if dt_fim else '31/12/2029'
128
+ url = (
129
+ f'{self.root_url}/estoqueporativo_e.asp?'
130
+ f'ativo={ativo}&dt_ini={dt_ini}&dt_fim={dt_fim}&moeda={moeda}&Op_exc={exec}'
131
+ )
132
+ if moeda == 1:
133
+ simb_moeda= 'R$'
134
+ else:
135
+ simb_moeda= 'USD'
136
+ df = get_response_to_pd(url,skiprows=4,timeout=timeout)
137
+ #df = df.iloc[2:]
138
+ df.columns = ['Data', 'Qtd Mercado', f'Volume Mercado ({simb_moeda} Mil)','Qtd Tesouraria',
139
+ f'Volume Tesouraria ({simb_moeda} Mil)','Qtd Total',
140
+ f'Volume Total ({simb_moeda} Mil)', 'Dump']
141
+ df = df.drop(columns=['Dump'])
142
+ return df
143
+
144
+ def estoque_por_periodo(self, dt_ini:str=None, dt_fim:str=None, moeda:int=None,timeout:int=None)->pd.DataFrame:
145
+ if moeda is not None and moeda not in [1, 2]:
146
+ raise ValueError("Parameter 'moeda' must be 1 or 2.")
147
+ moeda = moeda if isinstance(moeda, int) else 1
148
+ dt_ini = _format_date_for_url(dt_ini) if dt_ini else '02/03/1992'
149
+ dt_fim = _format_date_for_url(dt_fim) if dt_fim else '31/12/2029'
150
+ url = (
151
+ f'{self.root_url}/estoqueporperiodo_e.asp?'
152
+ f'dt_ini={dt_ini}&dt_fim={dt_fim}&moeda={moeda}'
153
+ )
154
+ df = get_response_to_pd(url,timeout=timeout)
155
+ return df.iloc[:,:-1]
156
+
157
+ def estoque_a_vencer(self, dt_ini:str=None, dt_fim:str=None, moeda:int=None,repactuacao:int=None,timeout:int=None)->pd.DataFrame:
158
+ dt_ini = _format_date_for_url(dt_ini) if dt_ini else '02/03/1992'
159
+ dt_fim = _format_date_for_url(dt_fim) if dt_fim else '31/12/2029'
160
+ if moeda is not None and moeda not in [1, 2]:
161
+ raise ValueError("Parameter 'moeda' must be 1 or 2.")
162
+ moeda = moeda if isinstance(moeda, int) else 1
163
+ if repactuacao is not None and repactuacao not in [1, 2]:
164
+ raise ValueError("Parameter 'moeda' must be 1 or 2.")
165
+ repactuacao = repactuacao if isinstance(repactuacao, int) else 1
166
+ url = (
167
+ f'{self.root_url}/estoqueavencer_e.asp?'
168
+ f'dt_ini={dt_ini}&dt_fim={dt_fim}&moeda={moeda}&rVen={repactuacao}'
169
+ )
170
+ df = get_response_to_pd(url,skiprows=4,timeout=timeout)
171
+ return df.iloc[:,:-1]
172
+
173
+ def estoque_relatorio(self, tipo:str, dt_ini:str=None, dt_fim:str=None, moeda:int=None,opcao:int=None,exec:str=None,timeout:int=None)->pd.DataFrame:
174
+ if tipo not in ['Indexadores', 'Tipo', 'Forma', 'Classe','Garantia','InstrucaoNormativa']:
175
+ raise ValueError("Parameter 'tipo' must be 'Indexadores', 'Tipo', 'Forma', 'Classe', 'Garantia' or 'InstrucaoNormativa'")
176
+ return _consulta_relatorio(self.root_url, tipo, dt_ini, dt_fim, moeda,opcao,exec,timeout)
177
+
@@ -0,0 +1,37 @@
1
+ from .utils.utils import get_response_to_pd, _format_cnpj, _format_date_for_url
2
+ from .__consulta_dados import UrlDebentures
3
+
4
+ class EventosFinanceiros:
5
+ def __init__(self)->str:
6
+ root_url = UrlDebentures().root_url
7
+ self.root_url = f'{root_url}/eventosfinanceiros'
8
+
9
+ def agenda_eventos(self, ativo:str = None, emissor:str = None, evento:str = None, dt_ini:str=None, dt_fim:str=None, dt_pgto_ini:str=None, dt_pgto_fim:str=None,timeout:int=None)->list:
10
+ ativo = ativo if isinstance(ativo, str) else ''
11
+ emissor = _format_cnpj(emissor) if emissor else ''
12
+ evento = evento if isinstance(evento, str) else ''
13
+ dt_ini = _format_date_for_url(dt_ini)
14
+ dt_fim = _format_date_for_url(dt_fim)
15
+ dt_pgto_ini = _format_date_for_url(dt_pgto_ini)
16
+ dt_pgto_fim = _format_date_for_url(dt_pgto_fim)
17
+ url = (
18
+ f'{self.root_url}/agenda_e.asp?'
19
+ f'emissor={emissor}&ativo={ativo}&evento={evento}&dt_ini={dt_ini}&dt_fim={dt_fim}'
20
+ f'&dt_pgto_ini={dt_pgto_ini}&dt_pgto_fim={dt_pgto_fim}'
21
+ )
22
+ df = get_response_to_pd(url,timeout=timeout)
23
+ return df
24
+
25
+ def pu_eventos(self, ativo:str = None, exec:str = None,emissor:str = None, evento:str = None, dt_ini:str=None, dt_fim:str=None,timeout:int=None)->list:
26
+ ativo = ativo if isinstance(ativo, str) else ''
27
+ emissor = _format_cnpj(emissor) if emissor else ''
28
+ exec = exec if isinstance(exec, str) else 'Nada'
29
+ evento = evento if isinstance(evento, str) else ''
30
+ dt_ini = _format_date_for_url(dt_ini)
31
+ dt_fim = _format_date_for_url(dt_fim)
32
+ url = (
33
+ f'{self.root_url}/pudeeventos_e.asp?'
34
+ f'op_exc={exec}&ativo={ativo}&evento={evento}&dt_ini={dt_ini}&dt_fim={dt_fim}&emissor={emissor}'
35
+ )
36
+ df = get_response_to_pd(url,timeout=timeout)
37
+ return df
@@ -0,0 +1,81 @@
1
+ import requests
2
+ import io
3
+ from dateutil import parser
4
+ import pandas as pd
5
+ from .utils.utils import get_response_to_pd, _format_cnpj, _format_date_for_url,simple_response_to_pd,get_soup_response_to_pd
6
+ from .__consulta_dados import UrlDebentures
7
+
8
+ class MercadoSecundario:
9
+ def __init__(self)->str:
10
+ root_url = UrlDebentures().root_url
11
+ self.root_url = f'https://www.anbima.com.br/informacoes/merc-sec-debentures/'
12
+ self.root_url_ = f'{root_url}/mercadosecundario'
13
+
14
+ def arquivo_precos_diario(self, data:str)->pd.DataFrame:
15
+ data_ = _format_date_for_url(data, '%y%m%d')
16
+ url = f'{self.root_url}/arqs/db{data_}.txt'
17
+ return simple_response_to_pd(url, '@')
18
+
19
+ #def vencidos_antecipadamente_diario(self, data:str)->pd.DataFrame:
20
+ # data_ = _format_date_for_url(data, '%d%b%Y')
21
+ # url = f'{self.root_url}/resultados/mdeb_{data_}_vencidos_antecipadamente.asp'
22
+ # df = get_response_to_pd(url)
23
+ # return df
24
+
25
+ #def ipca_spread_diario(self, data:str)->pd.DataFrame:
26
+ # data_ = _format_date_for_url(data, '%d%b%Y')
27
+ # url = f'{self.root_url}/resultados/mdeb_{data_}_ipca_spread.asp'
28
+ # df = get_response_to_pd(url)
29
+ # return df
30
+
31
+ #def igpm_spread_diario(self, data:str)->pd.DataFrame:
32
+ # data_ = _format_date_for_url(data, '%d%b%Y')
33
+ # url = f'{self.root_url}/resultados/mdeb_{data_}_igp-m.asp'
34
+ # df = get_response_to_pd(url)
35
+ # return df
36
+
37
+ def preco_negociacao(self, ativo:str = None, exec:str = None,emissor:str = None, dt_ini:str=None, dt_fim:str=None)->list:
38
+ ativo = ativo if isinstance(ativo, str) else ''
39
+ emissor = _format_cnpj(emissor) if emissor else ''
40
+ exec = exec if isinstance(exec, str) else 'Nada'
41
+ dt_ini = _format_date_for_url(dt_ini, '%Y%m%d') if dt_ini else '19900302'
42
+ dt_fim = _format_date_for_url(dt_fim, '%Y%m%d') if dt_fim else '20291231'
43
+ url = (
44
+ f'{self.root_url_}/precosdenegociacao_e.asp?'
45
+ f'op_exc={exec}&emissor={emissor}&ativo={ativo}&dt_ini={dt_ini}&dt_fim={dt_fim}'
46
+ )
47
+ df = get_response_to_pd(url)
48
+ return df
49
+
50
+ def volume_negociacao(self, dt_ini:str=None, dt_fim:str=None)->list:
51
+ dt_ini = _format_date_for_url(dt_ini, '%d/%m/%Y') if dt_ini else '02/03/1992'
52
+ dt_fim = _format_date_for_url(dt_fim, '%d/%m/%Y') if dt_fim else '31/12/2029'
53
+
54
+ url = f'{self.root_url_}/volumesnegociados_r.asp'
55
+
56
+ headers = {
57
+ "Host": "www.debentures.com.br",
58
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0",
59
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
60
+ "Accept-Language": "en-US,en;q=0.5",
61
+ "Accept-Encoding": "gzip, deflate, br, zstd",
62
+ "Content-Type": "application/x-www-form-urlencoded",
63
+ "Origin": "https://www.debentures.com.br",
64
+ "Connection": "keep-alive",
65
+ "Upgrade-Insecure-Requests": "1",
66
+ "Sec-Fetch-Dest": "document",
67
+ "Sec-Fetch-Mode": "navigate",
68
+ "Sec-Fetch-Site": "same-origin",
69
+ "Sec-Fetch-User": "?1",
70
+ "DNT": "1",
71
+ "Sec-GPC": "1",
72
+ "Priority": "u=0, i",
73
+ }
74
+
75
+ data = {
76
+ "dt_ini": f"{dt_ini}",
77
+ "dt_fim": f"{dt_fim}",
78
+ }
79
+
80
+ df = get_soup_response_to_pd(url, header_class='Ver10666666_cab', table_class='Tab10333333', headers=headers, data=data)
81
+ return df
File without changes
@@ -0,0 +1,156 @@
1
+ import requests
2
+ import io
3
+ import pandas as pd
4
+ import locale
5
+ from bs4 import BeautifulSoup
6
+ from dateutil import parser
7
+
8
+
9
+ def get_response_to_pd(url: str, sep:str = None, headers:dict=None, data:dict=None,timeout:int=None,skiprows:int=None) -> pd.DataFrame:
10
+ if sep is None:
11
+ sep = '|'
12
+ if timeout is None:
13
+ timeout= 10
14
+ if skiprows is None:
15
+ skiprows = 2
16
+ try:
17
+ response = requests.get(url, headers=headers, data=data, timeout=timeout)
18
+ response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
19
+ df = pd.read_csv(io.StringIO(response.text), sep=sep, encoding='utf-8', names=['raw'], skiprows=skiprows)
20
+ df = df['raw'].str.split('\t', expand=True).reset_index(drop=True)
21
+ df.columns = df.iloc[0]
22
+ df = df[1:].reset_index(drop=True)[:-2]
23
+ return df
24
+ except requests.exceptions.ConnectionError as e:
25
+ print(f"Connection Error for {url}: {e}")
26
+ return pd.DataFrame()
27
+ except requests.exceptions.Timeout as e:
28
+ print(f"Timeout Error for {url}: {e}")
29
+ return pd.DataFrame()
30
+ except requests.exceptions.HTTPError as e:
31
+ print(f"HTTP Error for {url}: {e}")
32
+ return pd.DataFrame()
33
+ except Exception as e:
34
+ print(f"An unexpected error occurred for {url}: {e}")
35
+ return pd.DataFrame()
36
+
37
+ def simple_response_to_pd(url:str, sep:str, encoding:str=None, names:list=None, skiprows:int=None) ->pd.DataFrame:
38
+ encoding = encoding if isinstance(encoding, str) else 'utf-8'
39
+ names = names if isinstance(names, list) else ['raw']
40
+ skiprows = skiprows if isinstance(skiprows, int) else 2
41
+ try:
42
+ response = requests.get(url, timeout=10)
43
+ response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
44
+ df = pd.read_csv(io.StringIO(response.text), sep=sep, encoding=encoding, names=names, skiprows=skiprows)
45
+ return df
46
+ except requests.exceptions.ConnectionError as e:
47
+ print(f"Connection Error for {url}: {e}")
48
+ return pd.DataFrame()
49
+ except requests.exceptions.Timeout as e:
50
+ print(f"Timeout Error for {url}: {e}")
51
+ return pd.DataFrame()
52
+ except requests.exceptions.HTTPError as e:
53
+ print(f"HTTP Error for {url}: {e}")
54
+ return pd.DataFrame()
55
+ except Exception as e:
56
+ print(f"An unexpected error occurred for {url}: {e}")
57
+ return pd.DataFrame()
58
+
59
+ def _format_cnpj(cnpj: str) -> str:
60
+ """
61
+ Formats a CNPJ string to be 14 digits, padding with leading zeros if necessary.
62
+ Returns an empty string if the input is not a valid CNPJ (e.g., non-numeric or too long).
63
+ """
64
+ if not isinstance(cnpj, str):
65
+ return ''
66
+
67
+ cnpj_digits = ''.join(filter(str.isdigit, cnpj))
68
+
69
+ if len(cnpj_digits) > 14:
70
+ return '' # CNPJ too long
71
+
72
+ return cnpj_digits.zfill(14)
73
+
74
+ def _format_date_for_url(date_input: str, dtfmt:str = None, locale_:str = None) -> str:
75
+ """
76
+ Converts a date string (in various formats) to DD%2FMM%2FYYYY for URL usage.
77
+ Returns an empty string if the date cannot be parsed.
78
+ """
79
+ if locale_ is None:
80
+ locale_ = 'pt_BR.utf8'
81
+ locale.setlocale(locale.LC_TIME, locale_)
82
+ if dtfmt is None:
83
+ dtfmt = '%d%%2F%m%%2F%Y'
84
+ if not isinstance(date_input, str) or not date_input:
85
+ return ''
86
+ try:
87
+ parsed_date = parser.parse(date_input)
88
+ return parsed_date.strftime(dtfmt)
89
+ except ValueError:
90
+ return '' # Invalid date format
91
+
92
+ def get_soup_response_to_pd(url: str, header_class:str = None, table_class:str = None, headers:dict=None, data:dict=None,timeout:int=None) ->pd.DataFrame:
93
+ if timeout is None:
94
+ timeout= 10
95
+ try:
96
+ response = requests.get(url, headers=headers, data=data, timeout=timeout)
97
+ response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
98
+ soup = BeautifulSoup(response.text, 'html.parser')
99
+ header_table = soup.find('table', class_=f'{header_class}')
100
+ headers = []
101
+ if header_table:
102
+ header_tds = header_table.find_all('td')
103
+ for td in header_tds:
104
+ text = td.get_text(strip=True)
105
+ if text:
106
+ headers.append(text)
107
+ else:
108
+ print(f"Could not find the header table (class='{header_class}').")
109
+ headers = ["Data de Negociação", "Volume Negociado em Moeda da Época"]
110
+
111
+ data_table = soup.find('table', class_=f'{table_class}')
112
+ extracted_data = []
113
+
114
+ if data_table:
115
+ rows = data_table.find_all('tr')
116
+ for row in rows:
117
+ cols = row.find_all('td')
118
+ cols = [ele.get_text(strip=True) for ele in cols]
119
+ cleaned_row = [col for col in cols if col]
120
+
121
+ if cleaned_row:
122
+ if "Total:" in cleaned_row[0] or "Total:" in cleaned_row[1]:
123
+ if len(cleaned_row) == 1 and "Total:" in cleaned_row[0]:
124
+ total_value_str = cleaned_row[0].replace("Total:", "").strip()
125
+ extracted_data.append(["Total", total_value_str])
126
+ elif len(cleaned_row) == 2 and "Total:" in cleaned_row[1]:
127
+ total_value_str = cleaned_row[1].replace("Total:", "").strip()
128
+ extracted_data.append(["Total", total_value_str])
129
+ else:
130
+ extracted_data.append(cleaned_row)
131
+ elif len(cleaned_row) == 2:
132
+ extracted_data.append(cleaned_row)
133
+ else:
134
+ print(f"Could not find the data table (class='{table_class}').")
135
+ if extracted_data and headers:
136
+ df = pd.DataFrame(extracted_data, columns=headers)
137
+ #print("\nDataFrame created successfully:")
138
+ elif extracted_data:
139
+ df = pd.DataFrame(extracted_data)
140
+ print("\nDataFrame created successfully (without specific headers):")
141
+ else:
142
+ df = pd.DataFrame()
143
+ print("No data extracted to form a DataFrame.")
144
+ return df
145
+ except requests.exceptions.ConnectionError as e:
146
+ print(f"Connection Error for {url}: {e}")
147
+ return pd.DataFrame()
148
+ except requests.exceptions.Timeout as e:
149
+ print(f"Timeout Error for {url}: {e}")
150
+ return pd.DataFrame()
151
+ except requests.exceptions.HTTPError as e:
152
+ print(f"HTTP Error for {url}: {e}")
153
+ return pd.DataFrame()
154
+ except Exception as e:
155
+ print(f"An unexpected error occurred for {url}: {e}")
156
+ return pd.DataFrame()
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.4
2
+ Name: debentures-dot-com
3
+ Version: 0.1.1
4
+ Summary: Um cliente em Python para consultar dados de debêntures do portal debentures.com.br
5
+ Author-email: gtazevedo <guilherme.tt.azevedo@gmail.com>
6
+ Project-URL: Homepage, https://github.com/gtazevedo/debentures_dot_com
7
+ Project-URL: Bug Tracker, https://github.com/gtazevedo/debentures_dot_com/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Office/Business :: Financial
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: requests
19
+ Requires-Dist: pandas
20
+ Requires-Dist: beautifulsoup4
21
+ Requires-Dist: python-dateutil
22
+ Dynamic: license-file
23
+
24
+ # debentures_dot_com
25
+
26
+ API para consultar dados do site [debentures.com.br](https://www.debentures.com.br/).
27
+
28
+ Esta API está atualmente em sua versão inicial; portanto, algumas funções ainda estão incompletas e nem todos os dados podem ser consultados no momento.
29
+
30
+ Atualmente, foram adicionadas consultas para os seguintes dados disponíveis na seção [Consulta a Dados](https://www.debentures.com.br/exploreosnd/consultaadados):
31
+
32
+ - **Emissões Debêntures** (acessível através da classe `EmissoesDebentures`)
33
+ - **Estoques Corporativos** (acessível através da classe `EstoquesCorporativos`)
34
+ - **Eventos Financeiros** (acessível através da classe `EventosFinanceiros`)
35
+ - **Mercado Secundário** (acessível através da classe `MercadoSecundario`)
36
+
37
+ ## Instalação
38
+ O pacote pode ser instalado via pip (exemplo):
39
+ ```bash
40
+ pip install debentures-dot-com
41
+ ```
42
+
43
+ ## Como Usar
44
+ Um exemplo rápido de como consultar emissões de debêntures públicas:
45
+
46
+ ```python
47
+ from debentures_dot_com import EmissoesDebentures
48
+
49
+ ed = EmissoesDebentures()
50
+ df = ed.lista_deb_publicas()
51
+ print(df.head())
52
+ ```
53
+
54
+ Para mais detalhes e testes de desenvolvimento, você pode verificar o arquivo `tests/dev.ipynb`.
@@ -0,0 +1,14 @@
1
+ debentures_dot_com/__consulta_dados.py,sha256=Zl6DnTC1bnImoUVLRAdH2S9Lo7iyKwb8gJ_mVq_-ZWA,133
2
+ debentures_dot_com/__init__.py,sha256=m2TmpzeSfFbkbrJqA99A7VAv94_EHF3RvLLUdImF2To,208
3
+ debentures_dot_com/__volumes.py,sha256=YLcgcBW6D9hKiaPy64e2YSRovaCR3xLSYw90kKvuFeU,827
4
+ debentures_dot_com/emissoes.py,sha256=oEQ-WX8AmsQiasTolLXZYjk6ArhlIfqa6Tb2NZdzLP8,9644
5
+ debentures_dot_com/estoques.py,sha256=RfyzguddQSOQU1uSkZ96sXeQvrZxelfjJltwqx5WNbo,8706
6
+ debentures_dot_com/eventos_fin.py,sha256=vRg7nQ6Iu0u5eM3cDfr5kVAKwHyi299REvt7s1srDGQ,1928
7
+ debentures_dot_com/mercados.py,sha256=ayPBsD2C94Zhn3FMYbHXSjGpz_8ArXjekDksbg17SbA,3630
8
+ debentures_dot_com/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ debentures_dot_com/utils/utils.py,sha256=7onBlgae5nGn1BaMDHC7SkLy8PipRnhWKuKDvtajwaw,6817
10
+ debentures_dot_com-0.1.1.dist-info/licenses/LICENSE,sha256=8BZPj1h3mE1GPJh9yA-zV4yHHm7fmWGKooXqRpmgZBQ,1087
11
+ debentures_dot_com-0.1.1.dist-info/METADATA,sha256=0C1x6XswBcocEaLu2RHh_4poiLz5gMLYOJeozchQoLo,2178
12
+ debentures_dot_com-0.1.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
13
+ debentures_dot_com-0.1.1.dist-info/top_level.txt,sha256=rsS9-rNuT6I6Lc8ye_foJB-LmFFy5kYd1KPtDrushHs,19
14
+ debentures_dot_com-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Guilherme
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ debentures_dot_com