PYield 0.26.0__tar.gz → 0.26.2__tar.gz

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.
Files changed (36) hide show
  1. {pyield-0.26.0 → pyield-0.26.2}/PKG-INFO +1 -1
  2. pyield-0.26.2/pyield/__about__.py +1 -0
  3. {pyield-0.26.0 → pyield-0.26.2}/pyield/__init__.py +10 -9
  4. pyield-0.26.2/pyield/bc/auction.py +308 -0
  5. pyield-0.26.2/pyield/ima.py +143 -0
  6. {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/ltn.py +2 -1
  7. {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/ntnf.py +2 -1
  8. {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/tools.py +1 -1
  9. pyield-0.26.0/pyield/__about__.py +0 -1
  10. {pyield-0.26.0 → pyield-0.26.2}/.gitignore +0 -0
  11. {pyield-0.26.0 → pyield-0.26.2}/LICENSE +0 -0
  12. {pyield-0.26.0 → pyield-0.26.2}/README.md +0 -0
  13. {pyield-0.26.0 → pyield-0.26.2}/pyield/anbima.py +0 -0
  14. {pyield-0.26.0 → pyield-0.26.2}/pyield/b3_futures/__init__.py +0 -0
  15. {pyield-0.26.0 → pyield-0.26.2}/pyield/b3_futures/common.py +0 -0
  16. {pyield-0.26.0/pyield → pyield-0.26.2/pyield/b3_futures}/di.py +0 -0
  17. {pyield-0.26.0 → pyield-0.26.2}/pyield/b3_futures/historical.py +0 -0
  18. {pyield-0.26.0 → pyield-0.26.2}/pyield/b3_futures/intraday.py +0 -0
  19. {pyield-0.26.0 → pyield-0.26.2}/pyield/b3_futures/xml.py +0 -0
  20. {pyield-0.26.0 → pyield-0.26.2}/pyield/bday.py +0 -0
  21. {pyield-0.26.0 → pyield-0.26.2}/pyield/conftest.py +0 -0
  22. {pyield-0.26.0 → pyield-0.26.2}/pyield/data_cache.py +0 -0
  23. {pyield-0.26.0 → pyield-0.26.2}/pyield/date_converter.py +0 -0
  24. {pyield-0.26.0 → pyield-0.26.2}/pyield/holidays/__init__.py +0 -0
  25. {pyield-0.26.0 → pyield-0.26.2}/pyield/holidays/br_holidays_new.txt +0 -0
  26. {pyield-0.26.0 → pyield-0.26.2}/pyield/holidays/br_holidays_old.txt +0 -0
  27. {pyield-0.26.0 → pyield-0.26.2}/pyield/holidays/brholidays.py +0 -0
  28. {pyield-0.26.0 → pyield-0.26.2}/pyield/indicators.py +0 -0
  29. {pyield-0.26.0 → pyield-0.26.2}/pyield/interpolator.py +0 -0
  30. {pyield-0.26.0 → pyield-0.26.2}/pyield/projections.py +0 -0
  31. {pyield-0.26.0 → pyield-0.26.2}/pyield/py.typed +0 -0
  32. {pyield-0.26.0 → pyield-0.26.2}/pyield/tools.py +0 -0
  33. {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/__init__.py +0 -0
  34. {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/lft.py +0 -0
  35. {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/ntnb.py +0 -0
  36. {pyield-0.26.0 → pyield-0.26.2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PYield
3
- Version: 0.26.0
3
+ Version: 0.26.2
4
4
  Summary: A Python library for analysis of fixed income instruments in Brazil
5
5
  Project-URL: Source, https://github.com/crdcj/PYield
6
6
  Author-email: Carlos Carvalho <cr.cj@outlook.com>
@@ -0,0 +1 @@
1
+ __version__ = "0.26.2"
@@ -1,26 +1,27 @@
1
- from pyield import anbima, bday, di, tools
1
+ from pyield import anbima, bday
2
2
  from pyield.__about__ import __version__
3
3
  from pyield.b3_futures import futures
4
- from pyield.di import DIFutures
4
+ from pyield.b3_futures.di import DIFutures
5
+ from pyield.ima import ima
5
6
  from pyield.indicators import indicator
6
7
  from pyield.interpolator import Interpolator
7
8
  from pyield.projections import projection
9
+ from pyield.tools import forward_rates
8
10
  from pyield.tpf import lft, ltn, ntnb, ntnf
9
11
 
10
12
  __all__ = [
11
13
  "__version__",
12
14
  "anbima",
13
15
  "bday",
16
+ "DIFutures",
17
+ "forward_rates",
14
18
  "futures",
19
+ "ima",
15
20
  "indicator",
21
+ "Interpolator",
22
+ "lft",
23
+ "ltn",
16
24
  "ntnb",
17
25
  "ntnf",
18
- "ltn",
19
- "lft",
20
- "DIFutures",
21
26
  "projection",
22
- "Interpolator",
23
- "tools",
24
- "di",
25
- "DIFutures",
26
27
  ]
@@ -0,0 +1,308 @@
1
+ """
2
+ Documentação da API do BC
3
+ https://olinda.bcb.gov.br/olinda/servico/leiloes_selic/versao/v1/aplicacao#!/recursos/leiloesTitulosPublicos#eyJmb3JtdWxhcmlvIjp7IiRmb3JtYXQiOiJqc29uIiwiJHRvcCI6MTAwfX0=
4
+ """
5
+
6
+ import io
7
+ import logging
8
+ from typing import Literal
9
+
10
+ import pandas as pd
11
+ import requests
12
+
13
+ import pyield as yd
14
+ from pyield import date_converter as dc
15
+ from pyield.date_converter import DateScalar
16
+ from pyield.tpf.ntnb import duration as duration_b
17
+ from pyield.tpf.ntnf import duration as duration_f
18
+
19
+ """Dicionário com o mapeamento das colunas da API do BC para o DataFrame final
20
+ Chaves com comentário serão descartadas ao final do processamento
21
+ A ordem das chaves será a ordem das colunas no DataFrame final
22
+ FR = First Round and SR = Second Round
23
+ """
24
+ COLUMN_MAPPING = {
25
+ # "id": "ID",
26
+ "dataMovimento": "Date",
27
+ "dataLiquidacao": "Settlement",
28
+ "tipoOferta": "AuctionType", # ['Venda', 'Compra', 'Tomador']
29
+ # "ofertante": "Ofertante", # ['Tesouro Nacional', 'Banco Central']
30
+ "edital": "Ordinance",
31
+ "tipoPublico": "Buyer", # ['TodoMercado', 'SomenteDealerApto', 'SomenteDealer']
32
+ # "prazo": "...", # N. de DC entre a data de liquidação e a data de vencimento
33
+ "codigoTitulo": "SelicCode", # [100000, 210100, 450000, 760199, 950199]
34
+ "dataVencimento": "Maturity",
35
+ "financeiro": "TotalValue", # = FR + SR (in millions)
36
+ "quantidadeOfertada": "OfferedQuantity",
37
+ "quantidadeAceita": "AcceptedQuantity",
38
+ "quantidadeOfertadaSegundaRodada": "OfferedQuantitySR",
39
+ "quantidadeAceitaSegundaRodada": "AcceptedQuantitySR",
40
+ "cotacaoMedia": "AvgPrice",
41
+ "cotacaoCorte": "CutPrice",
42
+ "taxaMedia": "AvgRate",
43
+ "taxaCorte": "CutRate",
44
+ }
45
+
46
+
47
+ def _load_from_url(url: str) -> pd.DataFrame:
48
+ response = requests.get(url, timeout=10)
49
+ response.raise_for_status()
50
+ return pd.read_csv(
51
+ io.StringIO(response.text),
52
+ dtype_backend="numpy_nullable",
53
+ decimal=",",
54
+ date_format="%Y-%m-%d %H:%M:%S",
55
+ parse_dates=["dataMovimento", "dataLiquidacao", "dataVencimento"],
56
+ )
57
+
58
+
59
+ def _pre_process_df(df: pd.DataFrame) -> pd.DataFrame:
60
+ # Only Tesouro Nacional auctions are considered
61
+ df = df.query("ofertante == 'Tesouro Nacional'").reset_index(drop=True)
62
+
63
+ # Remover colunas que não serão utilizadas
64
+ keep_columns = [col for col in COLUMN_MAPPING.keys() if col in df.columns]
65
+ return df[keep_columns].rename(columns=COLUMN_MAPPING)
66
+
67
+
68
+ def _process_df(df: pd.DataFrame) -> pd.DataFrame:
69
+ # Second Round (SR) columns can be null -> remove nulls to avoid propagation
70
+ df["AcceptedQuantitySR"] = df["AcceptedQuantitySR"].fillna(0)
71
+ df["OfferedQuantitySR"] = df["OfferedQuantitySR"].fillna(0)
72
+
73
+ # Calcular o financeiro só do leilão
74
+ total_accepted_quantity = df["AcceptedQuantity"] + df["AcceptedQuantitySR"]
75
+ first_round_ratio = df["AcceptedQuantity"] / total_accepted_quantity
76
+ first_round_ratio = df["AcceptedQuantity"] / total_accepted_quantity
77
+ first_round_ratio = first_round_ratio.where(df["AcceptedQuantity"] != 0, 0)
78
+ # O dado do financeiro do BC está em milhões com uma casa decimal de precisão
79
+ # Portanto, podemos converter para inteiro sem perda de informação
80
+ df["Value"] = (first_round_ratio * df["TotalValue"]).round(1)
81
+ df["Value"] = (first_round_ratio * df["TotalValue"]).round(1)
82
+ df["Value"] = (df["Value"] * 1_000_000).astype("Int64")
83
+
84
+ # Calcular o financeiro só da SV
85
+ second_round_ratio = df["AcceptedQuantitySR"] / total_accepted_quantity
86
+ second_round_ratio = second_round_ratio.where(df["AcceptedQuantitySR"] != 0, 0)
87
+ df["ValueSR"] = (second_round_ratio * df["TotalValue"]).round(1)
88
+ df["ValueSR"] = (df["ValueSR"] * 1_000_000).astype("Int64")
89
+
90
+ bond_mappping = {
91
+ 100000: "LTN",
92
+ 210100: "LFT",
93
+ # 450000: "..." # Foi um título ofertado pelo BC em 2009/2010
94
+ 760199: "NTN-B",
95
+ 950199: "NTN-F",
96
+ }
97
+ df["BondType"] = df["SelicCode"].map(bond_mappping).astype("string")
98
+
99
+ # Em 11/06/2024 o BC passou a informar nas colunas de cotacao os valores dos PUs
100
+ # Isso afeta somente os títulos LFT e NTN-B
101
+ change_Date = pd.Timestamp("2024-06-11")
102
+ adjusted_price = (df["Value"] / df["AcceptedQuantity"]).round(6)
103
+ is_after_change_Date = df["Date"] >= change_Date
104
+ is_ltn_or_ntnf = df["BondType"].isin(["LTN", "NTN-F"])
105
+ keep_avg_price = is_after_change_Date | is_ltn_or_ntnf
106
+ df["AvgPrice"] = df["AvgPrice"].where(keep_avg_price, adjusted_price)
107
+
108
+ return df
109
+
110
+
111
+ def _adjust_null_values(df: pd.DataFrame) -> pd.DataFrame:
112
+ # Onde não há quantidade aceita, não há nem taxa e nem PU definidos.
113
+ is_accepted = df["AcceptedQuantity"] != 0
114
+ df["AvgRate"] = df["AvgRate"].where(is_accepted, pd.NA)
115
+ df["CutRate"] = df["CutRate"].where(is_accepted, pd.NA)
116
+ df["AvgPrice"] = df["AvgPrice"].where(is_accepted, pd.NA)
117
+ df["CutPrice"] = df["CutPrice"].where(is_accepted, pd.NA)
118
+
119
+ return df
120
+
121
+
122
+ def _add_dv01(df: pd.DataFrame) -> pd.DataFrame:
123
+ df["BDToMat"] = yd.bday.count(start=df["Date"], end=df["Maturity"])
124
+
125
+ is_accepted = df["AcceptedQuantity"] != 0 # noqa
126
+ df_not_accepted = df.query("~@is_accepted").reset_index(drop=True)
127
+ df_is_accepted = df.query("@is_accepted").reset_index(drop=True)
128
+
129
+ df_lft = df_is_accepted.query("BondType == 'LFT'").reset_index(drop=True)
130
+
131
+ df_ltn = df_is_accepted.query("BondType == 'LTN'").reset_index(drop=True)
132
+ if not df_ltn.empty:
133
+ df_ltn["Duration"] = df_ltn["BDToMat"] / 252
134
+ df_ltn["MDuration"] = df_ltn["Duration"] / (1 + df_ltn["AvgRate"] / 100)
135
+ df_ltn["DV01"] = 0.0001 * df_ltn["MDuration"] * df_ltn["AvgPrice"]
136
+
137
+ def compute_f_duration(row):
138
+ return duration_f(row["Date"], row["Maturity"], row["CutRate"] / 100)
139
+
140
+ df_ntnf = df_is_accepted.query("BondType == 'NTN-F'").reset_index(drop=True)
141
+ if not df_ntnf.empty:
142
+ df_ntnf["Duration"] = df_ntnf.apply(compute_f_duration, axis=1)
143
+ df_ntnf["Duration"] = df_ntnf["Duration"].astype("Float64")
144
+ df_ntnf["MDuration"] = df_ntnf["Duration"] / (1 + df_ntnf["AvgRate"] / 100)
145
+ df_ntnf["DV01"] = 0.0001 * df_ntnf["MDuration"] * df_ntnf["AvgPrice"]
146
+
147
+ def compute_b_duration(row):
148
+ return duration_b(row["Date"], row["Maturity"], row["CutRate"] / 100)
149
+
150
+ df_ntnb = df_is_accepted.query("BondType == 'NTN-B'").reset_index(drop=True)
151
+ if not df_ntnb.empty:
152
+ df_ntnb["Duration"] = df_ntnb.apply(compute_b_duration, axis=1)
153
+ df_ntnb["Duration"] = df_ntnb["Duration"].astype("Float64")
154
+ df_ntnb["MDuration"] = df_ntnb["Duration"] / (1 + df_ntnb["AvgRate"] / 100)
155
+ df_ntnb["DV01"] = 0.0001 * df_ntnb["MDuration"] * df_ntnb["AvgPrice"]
156
+
157
+ df = pd.concat([df_not_accepted, df_lft, df_ltn, df_ntnf, df_ntnb])
158
+
159
+ df["DV01"] *= df["AcceptedQuantity"]
160
+ df["DV01"] = df["DV01"].round(0).astype("Int64")
161
+
162
+ return df
163
+
164
+
165
+ def _filter_auction_by_type(
166
+ df: pd.DataFrame, auction_type: Literal["Sell", "Buy"]
167
+ ) -> pd.DataFrame:
168
+ auction_type_mapping = {"Venda": "Sell", "Compra": "Buy"}
169
+ auction_type = auction_type_mapping.get(auction_type)
170
+ if auction_type:
171
+ df = df.query(f"AuctionType == '{auction_type}'").reset_index(drop=True)
172
+ return df
173
+
174
+
175
+ def _sort_and_reorder_columns(df: pd.DataFrame) -> pd.DataFrame:
176
+ column_sequence = [
177
+ "Date",
178
+ "Settlement",
179
+ "AuctionType",
180
+ "Ordinance",
181
+ "Buyer",
182
+ "BondType",
183
+ "Maturity",
184
+ "SelicCode",
185
+ "TotalValue",
186
+ "Value",
187
+ "ValueSR",
188
+ "OfferedQuantity",
189
+ "OfferedQuantitySR",
190
+ "AcceptedQuantity",
191
+ "AcceptedQuantitySR",
192
+ "AvgPrice",
193
+ "CutPrice",
194
+ "AvgRate",
195
+ "CutRate",
196
+ "BDToMat",
197
+ "Duration",
198
+ "MDuration",
199
+ "DV01",
200
+ ]
201
+
202
+ primary_sort_keys = ["Date", "AuctionType", "BondType", "Maturity"]
203
+ return df[column_sequence].sort_values(by=primary_sort_keys).reset_index(drop=True)
204
+
205
+
206
+ def _fetch_df_from_url(
207
+ url: str,
208
+ auction_type: Literal["Sell", "Buy"] = None,
209
+ ) -> pd.DataFrame:
210
+ try:
211
+ df = _load_from_url(url)
212
+ df = _pre_process_df(df)
213
+ df = _adjust_null_values(df)
214
+ df = _process_df(df)
215
+ df = _add_dv01(df)
216
+ df = _filter_auction_by_type(df, auction_type)
217
+ df = _sort_and_reorder_columns(df)
218
+ return df
219
+ except Exception as e:
220
+ logging.error(f"Error on fetching auction data from BC: {e}")
221
+ return pd.DataFrame()
222
+
223
+
224
+ def get_all_auctions() -> pd.DataFrame:
225
+ """
226
+ Retrieves a DataFrame containing all public auction data from the BC.
227
+
228
+ This function fetches the complete series of public auctions conducted by the
229
+ National Treasury via the BC API and processes it to a standardized DataFrame.
230
+
231
+ Returns:
232
+ pd.DataFrame: A DataFrame sorted by Date, AuctionType, BondType, and Maturity
233
+ containing auction data. In case of an error during data retrieval, an empty
234
+ DataFrame is returned and an error message is logged.
235
+ """
236
+ # URL com toda a série de leilões de títulos públicos do Tesouro Nacional
237
+ url = "https://olinda.bcb.gov.br/olinda/servico/leiloes_selic/versao/v1/odata/leiloesTitulosPublicos(dataMovimentoInicio=@dataMovimentoInicio,dataMovimentoFim=@dataMovimentoFim,dataLiquidacao=@dataLiquidacao,codigoTitulo=@codigoTitulo,dataVencimento=@dataVencimento,edital=@edital,tipoPublico=@tipoPublico,tipoOferta=@tipoOferta)?$format=text/csv"
238
+ return _fetch_df_from_url(url)
239
+
240
+
241
+ def auctions(
242
+ start: DateScalar,
243
+ end: DateScalar | None = None,
244
+ auction_type: Literal["Sell", "Buy"] = None,
245
+ ) -> pd.DataFrame:
246
+ """
247
+ Retrieves auction data for a given date range and auction type.
248
+
249
+ It fetches auction data from the BC API for the specified start and end dates.
250
+ If the end date is not provided, it defaults to the start date.
251
+ The resulting data is then filtered by the specified auction type ("Sell" or "Buy").
252
+ "Sell" auctions are those where the National Treasury sells bonds to the market.
253
+ "Buy" auctions are those where the National Treasury buys bonds from the market.
254
+
255
+ Args:
256
+ start (DateScalar): The start date for the auction data query.
257
+ end (DateScalar | None, optional): The end date for the auction data query.
258
+ If None, the start date is used. Defaults to None.
259
+ auction_type (Literal["Sell", "Buy"], optional): The type of auction to filter
260
+ by. Defaults to "Sell".
261
+
262
+ Returns:
263
+ pd.DataFrame: A DataFrame containing auction data for the specified date range
264
+ and auction type. In case of an error during data retrieval, an empty
265
+ DataFrame is returned and an error message is logged.
266
+
267
+ Notes:
268
+ FR = First Round
269
+ SR = Second Round
270
+
271
+ The DataFrame has the following columns:
272
+ - Date: Date of the auction.
273
+ - Settlement: Settlement date of the auction.
274
+ - AuctionType: Type of auction (e.g., "Sell" or "Buy").
275
+ - Ordinance: Normative ordinance associated with the auction.
276
+ - Buyer: Buyer category (e.g., "TodoMercado", "SomenteDealerApto").
277
+ - BondType: Category of the bond (e.g., "LTN", "LFT", "NTN-B", "NTN-F").
278
+ - Maturity: Bond maturity date.
279
+ - SelicCode: Bond code in the Selic system.
280
+ - TotalValue: Total value of the auction: FR + SR.
281
+ - Value: First round (FR) value of the auction.
282
+ - ValueSR: Value in the second round.
283
+ - OfferedQuantity: Quantity offered in the auction.
284
+ - OfferedQuantitySR: Quantity offered in the second round.
285
+ - AcceptedQuantity: Quantity accepted in the auction.
286
+ - AcceptedQuantitySR: Quantity accepted in the second round.
287
+ - AvgPrice: Average price in the auction.
288
+ - CutPrice: Cut-off price.
289
+ - AvgRate: Average interest rate.
290
+ - CutRate: Cut-off rate.
291
+ - BDToMat: Business days to maturity.
292
+ - Duration: Duration of the bond.
293
+ - MDuration: Modified duration of the bond.
294
+ - DV01: Dollar value of a 1 bp change in yield.
295
+
296
+ """
297
+
298
+ start = dc.convert_input_dates(start)
299
+ if end:
300
+ end = dc.convert_input_dates(end)
301
+ else:
302
+ end = start
303
+
304
+ start_str = start.strftime("%Y-%m-%d")
305
+ end_str = end.strftime("%Y-%m-%d")
306
+ url = f"https://olinda.bcb.gov.br/olinda/servico/leiloes_selic/versao/v1/odata/leiloesTitulosPublicos(dataMovimentoInicio=@dataMovimentoInicio,dataMovimentoFim=@dataMovimentoFim,dataLiquidacao=@dataLiquidacao,codigoTitulo=@codigoTitulo,dataVencimento=@dataVencimento,edital=@edital,tipoPublico=@tipoPublico,tipoOferta=@tipoOferta)?@dataMovimentoInicio='{start_str}'&@dataMovimentoFim='{end_str}'&$format=text/csv"
307
+
308
+ return _fetch_df_from_url(url, auction_type)
@@ -0,0 +1,143 @@
1
+ import io
2
+ import logging
3
+ import re
4
+ import warnings
5
+
6
+ import pandas as pd
7
+ import requests
8
+ from bs4 import XMLParsedAsHTMLWarning
9
+
10
+ from pyield import date_converter as dc
11
+ from pyield.date_converter import DateScalar
12
+
13
+ IMA_URL = "https://www.anbima.com.br/informacoes/ima/ima-quantidade-mercado.asp"
14
+ COLUMN_MAPPING = {
15
+ "Data Referência": "Date",
16
+ "Título": "BondType",
17
+ "Data de Vencimento": "Maturity",
18
+ "Codigo Selic": "SelicCode",
19
+ "Código ISIN": "ISIN",
20
+ "PU (R$)": "Price",
21
+ "Quantidade em Mercado (1.000 Títulos)": "MarketQuantity",
22
+ "Valor de Mercado (R$ Mil)": "MarketValue",
23
+ "Variação da Quantidade (1.000 Títulos)": "QuantityVariation",
24
+ "Status do Titulo": "BondStatus",
25
+ }
26
+
27
+
28
+ def _fetch_url_tables(target_date: pd.Timestamp) -> pd.DataFrame:
29
+ # Warning supression for BeautifulSoup, as the text is valid HTML and not XML
30
+ warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning)
31
+
32
+ target_date_str = target_date.strftime("%d/%m/%Y")
33
+ payload = {
34
+ "Tipo": "",
35
+ "DataRef": "",
36
+ "Pai": "ima",
37
+ "Dt_Ref_Ver": "20250117",
38
+ "Dt_Ref": f"{target_date_str}",
39
+ }
40
+
41
+ r = requests.post(IMA_URL, data=payload, timeout=10)
42
+ r.raise_for_status()
43
+ r.encoding = "iso-8859-1"
44
+ if "Não há dados disponíveis" in r.text:
45
+ logging.warning("No data found. Returning empty DataFrame.")
46
+ return pd.DataFrame()
47
+
48
+ string_io_buffer = io.StringIO(r.text)
49
+
50
+ dfs = pd.read_html(
51
+ string_io_buffer,
52
+ flavor="html5lib",
53
+ attrs={"width": "100%"},
54
+ header=0,
55
+ thousands=".",
56
+ decimal=",",
57
+ displayed_only=True,
58
+ dtype_backend="numpy_nullable",
59
+ )
60
+
61
+ df = (
62
+ pd.concat(dfs)
63
+ .query("`Data de Vencimento`.notnull()")
64
+ .query("Título!='Título'")
65
+ .drop_duplicates(subset="Código ISIN")
66
+ .reset_index(drop=True)
67
+ )
68
+
69
+ # Convert to CSV and then back to pandas to get automatic type conversion
70
+ csv_buffer = io.StringIO(df.to_csv(index=False))
71
+ df = pd.read_csv(csv_buffer, dtype_backend="numpy_nullable")
72
+
73
+ # Expressão regular para datas no formato dd/mm/ano
74
+ date_pattern = r"\b(\d{2}/\d{2}/\d{4})\b"
75
+
76
+ # Busca pela primeira data
77
+ match = re.search(date_pattern, r.text)
78
+
79
+ if match:
80
+ anbima_date_str = match.group(1)
81
+ anbima_date = pd.to_datetime(anbima_date_str, format="%d/%m/%Y")
82
+ else:
83
+ logging.warning("No date found. Returning empty DataFrame.")
84
+ return pd.DataFrame()
85
+
86
+ df["Data Referência"] = anbima_date
87
+
88
+ return df
89
+
90
+
91
+ def _process_df(df: pd.DataFrame) -> pd.DataFrame:
92
+ df = df.rename(columns=COLUMN_MAPPING)[COLUMN_MAPPING.values()]
93
+ df["Maturity"] = pd.to_datetime(df["Maturity"], format="%d/%m/%Y")
94
+ # Converter a valores para inteiros
95
+ df["MarketQuantity"] = (1000 * df["MarketQuantity"]).astype("Int64")
96
+ df["MarketValue"] = (1000 * df["MarketValue"]).astype("Int64")
97
+ df["QuantityVariation"] = (1000 * df["QuantityVariation"]).astype("Int64")
98
+
99
+ return df
100
+
101
+
102
+ def ima(date: DateScalar) -> pd.DataFrame:
103
+ """
104
+ Fetch and process IMA market data for a given date.
105
+
106
+ This function retrieves IMA market data for the specified date from ANBIMA,
107
+ processes the data into a structured DataFrame, and returns the resulting DataFrame.
108
+ It handles conversion of date formats, renames columns to English, and converts
109
+ certain numeric columns to integer types. In the event of an error during data
110
+ fetching or processing, an empty DataFrame is returned.
111
+
112
+ Args:
113
+ date (DateScalar): A date-like object representing the target date for fetching
114
+ the data.
115
+
116
+ Returns:
117
+ pd.DataFrame: A DataFrame containing the IMA data with the following columns:
118
+ - Date: Reference date of the data.
119
+ - BondType: Type of bond.
120
+ - Maturity: Bond maturity date.
121
+ - SelicCode: Code representing the SELIC rate.
122
+ - ISIN: International Securities Identification Number.
123
+ - Price: Bond price.
124
+ - MarketQuantity: Market quantity (in units of 1000 bonds).
125
+ - MarketValue: Market value (in units of 1000 reais).
126
+ - QuantityVariation: Variation in quantity (in units of 1000 bonds).
127
+ - BondStatus: Status of the bond.
128
+
129
+ Raises:
130
+ Exception: Logs error and returns an empty DataFrame if any error occurs during
131
+ fetching or processing.
132
+
133
+ """
134
+ try:
135
+ date = dc.convert_input_dates(date)
136
+ df = _fetch_url_tables(date)
137
+ df = _process_df(df)
138
+ return df
139
+ except Exception as e:
140
+ date_str = date.strftime("%d/%m/%Y")
141
+ logging.error(f"Error fetching IMA for {date_str}. Returning empty DataFrame.")
142
+ logging.error(f"Error message: {e}", exc_info=True)
143
+ return pd.DataFrame()
@@ -1,7 +1,8 @@
1
1
  import pandas as pd
2
2
 
3
- from pyield import anbima, bday, di
3
+ from pyield import anbima, bday
4
4
  from pyield import date_converter as dc
5
+ from pyield.b3_futures import di
5
6
  from pyield.date_converter import DateScalar
6
7
  from pyield.tpf import tools as tt
7
8
 
@@ -3,9 +3,10 @@ from collections.abc import Callable
3
3
  import numpy as np
4
4
  import pandas as pd
5
5
 
6
- from pyield import anbima, bday, data_cache, di
6
+ from pyield import anbima, bday, data_cache
7
7
  from pyield import date_converter as dc
8
8
  from pyield import interpolator as ip
9
+ from pyield.b3_futures import di
9
10
  from pyield.date_converter import DateScalar
10
11
  from pyield.tpf import tools as tt
11
12
 
@@ -5,8 +5,8 @@ import pandas as pd
5
5
 
6
6
  from pyield import anbima
7
7
  from pyield import date_converter as dc
8
+ from pyield.b3_futures.di import DIFutures
8
9
  from pyield.date_converter import DateScalar
9
- from pyield.di import DIFutures
10
10
 
11
11
 
12
12
  @overload
@@ -1 +0,0 @@
1
- __version__ = "0.26.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes