PYield 0.26.1__tar.gz → 0.26.3__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.1 → pyield-0.26.3}/PKG-INFO +1 -1
  2. pyield-0.26.3/pyield/__about__.py +1 -0
  3. {pyield-0.26.1 → pyield-0.26.3}/pyield/__init__.py +1 -1
  4. pyield-0.26.3/pyield/bc/auction.py +322 -0
  5. {pyield-0.26.1 → pyield-0.26.3}/pyield/tpf/ltn.py +2 -1
  6. {pyield-0.26.1 → pyield-0.26.3}/pyield/tpf/ntnf.py +2 -1
  7. {pyield-0.26.1 → pyield-0.26.3}/pyield/tpf/tools.py +1 -1
  8. pyield-0.26.1/pyield/__about__.py +0 -1
  9. {pyield-0.26.1 → pyield-0.26.3}/.gitignore +0 -0
  10. {pyield-0.26.1 → pyield-0.26.3}/LICENSE +0 -0
  11. {pyield-0.26.1 → pyield-0.26.3}/README.md +0 -0
  12. {pyield-0.26.1 → pyield-0.26.3}/pyield/anbima.py +0 -0
  13. {pyield-0.26.1 → pyield-0.26.3}/pyield/b3_futures/__init__.py +0 -0
  14. {pyield-0.26.1 → pyield-0.26.3}/pyield/b3_futures/common.py +0 -0
  15. {pyield-0.26.1/pyield → pyield-0.26.3/pyield/b3_futures}/di.py +0 -0
  16. {pyield-0.26.1 → pyield-0.26.3}/pyield/b3_futures/historical.py +0 -0
  17. {pyield-0.26.1 → pyield-0.26.3}/pyield/b3_futures/intraday.py +0 -0
  18. {pyield-0.26.1 → pyield-0.26.3}/pyield/b3_futures/xml.py +0 -0
  19. {pyield-0.26.1 → pyield-0.26.3}/pyield/bday.py +0 -0
  20. {pyield-0.26.1 → pyield-0.26.3}/pyield/conftest.py +0 -0
  21. {pyield-0.26.1 → pyield-0.26.3}/pyield/data_cache.py +0 -0
  22. {pyield-0.26.1 → pyield-0.26.3}/pyield/date_converter.py +0 -0
  23. {pyield-0.26.1 → pyield-0.26.3}/pyield/holidays/__init__.py +0 -0
  24. {pyield-0.26.1 → pyield-0.26.3}/pyield/holidays/br_holidays_new.txt +0 -0
  25. {pyield-0.26.1 → pyield-0.26.3}/pyield/holidays/br_holidays_old.txt +0 -0
  26. {pyield-0.26.1 → pyield-0.26.3}/pyield/holidays/brholidays.py +0 -0
  27. {pyield-0.26.1 → pyield-0.26.3}/pyield/ima.py +0 -0
  28. {pyield-0.26.1 → pyield-0.26.3}/pyield/indicators.py +0 -0
  29. {pyield-0.26.1 → pyield-0.26.3}/pyield/interpolator.py +0 -0
  30. {pyield-0.26.1 → pyield-0.26.3}/pyield/projections.py +0 -0
  31. {pyield-0.26.1 → pyield-0.26.3}/pyield/py.typed +0 -0
  32. {pyield-0.26.1 → pyield-0.26.3}/pyield/tools.py +0 -0
  33. {pyield-0.26.1 → pyield-0.26.3}/pyield/tpf/__init__.py +0 -0
  34. {pyield-0.26.1 → pyield-0.26.3}/pyield/tpf/lft.py +0 -0
  35. {pyield-0.26.1 → pyield-0.26.3}/pyield/tpf/ntnb.py +0 -0
  36. {pyield-0.26.1 → pyield-0.26.3}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PYield
3
- Version: 0.26.1
3
+ Version: 0.26.3
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.3"
@@ -1,7 +1,7 @@
1
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
5
  from pyield.ima import ima
6
6
  from pyield.indicators import indicator
7
7
  from pyield.interpolator import Interpolator
@@ -0,0 +1,322 @@
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": "Value", # = FR + SR (in millions)
36
+ "quantidadeOfertada": "OfferedQuantityFR",
37
+ "quantidadeAceita": "AcceptedQuantityFR",
38
+ "quantidadeOfertadaSegundaRodada": "OfferedQuantitySR",
39
+ "quantidadeAceitaSegundaRodada": "AcceptedQuantitySR",
40
+ "cotacaoMedia": "AvgPrice",
41
+ "cotacaoCorte": "CutPrice",
42
+ "taxaMedia": "AvgRate",
43
+ "taxaCorte": "CutRate",
44
+ }
45
+
46
+ BASE_API_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)?"
47
+
48
+
49
+ def _load_from_url(url: str) -> pd.DataFrame:
50
+ response = requests.get(url, timeout=10)
51
+ response.raise_for_status()
52
+ return pd.read_csv(
53
+ io.StringIO(response.text),
54
+ dtype_backend="numpy_nullable",
55
+ decimal=",",
56
+ date_format="%Y-%m-%d %H:%M:%S",
57
+ parse_dates=["dataMovimento", "dataLiquidacao", "dataVencimento"],
58
+ )
59
+
60
+
61
+ def _pre_process_df(df: pd.DataFrame) -> pd.DataFrame:
62
+ # Only Tesouro Nacional auctions are considered
63
+ df = df.query("ofertante == 'Tesouro Nacional'").reset_index(drop=True)
64
+
65
+ # Remover colunas que não serão utilizadas
66
+ keep_columns = [col for col in COLUMN_MAPPING.keys() if col in df.columns]
67
+ return df[keep_columns].rename(columns=COLUMN_MAPPING)
68
+
69
+
70
+ def _process_df(df: pd.DataFrame) -> pd.DataFrame:
71
+ # Second Round (SR) columns can be null -> remove nulls to avoid propagation
72
+ df["AcceptedQuantitySR"] = df["AcceptedQuantitySR"].fillna(0)
73
+ df["OfferedQuantitySR"] = df["OfferedQuantitySR"].fillna(0)
74
+
75
+ df["OfferedQuantity"] = df["OfferedQuantityFR"] + df["OfferedQuantitySR"]
76
+ df["AcceptedQuantity"] = df["AcceptedQuantityFR"] + df["AcceptedQuantitySR"]
77
+ # Total value of the auction in R$ millions -> convert it to unit value
78
+ df["Value"] = (1_000_000 * df["Value"]).round(0).astype("Int64")
79
+
80
+ # Remove the percentage sign and round to 6 decimal places (4 decimal places in %)
81
+ # Before 25/08/2015, there were no rounding rules for the rates in the BC API
82
+ df["AvgRate"] = (df["AvgRate"] / 100).round(6)
83
+ df["CutRate"] = (df["CutRate"] / 100).round(6)
84
+
85
+ # Calculate the financial value of the first round
86
+ first_round_ratio = df["AcceptedQuantityFR"] / df["AcceptedQuantity"]
87
+ # Force 0 when AcceptedQuantityFR is 0 to avoid division by zero
88
+ first_round_ratio = first_round_ratio.where(df["AcceptedQuantityFR"] != 0, 0)
89
+ # O dado do financeiro do BC está em milhões com uma casa decimal de precisão
90
+ # Portanto, podemos converter para inteiro sem perda de informação
91
+ df["ValueFR"] = (first_round_ratio * df["Value"]).round(0).astype("Int64")
92
+
93
+ # Calculate the financial value of the second round
94
+ second_round_ratio = df["AcceptedQuantitySR"] / df["AcceptedQuantity"]
95
+ second_round_ratio = second_round_ratio.where(df["AcceptedQuantitySR"] != 0, 0)
96
+ df["ValueSR"] = (second_round_ratio * df["Value"]).round(0).astype("Int64")
97
+
98
+ bond_mappping = {
99
+ 100000: "LTN",
100
+ 210100: "LFT",
101
+ # 450000: "..." # Foi um título ofertado pelo BC em 2009/2010
102
+ 760199: "NTN-B",
103
+ 950199: "NTN-F",
104
+ }
105
+ df["BondType"] = df["SelicCode"].map(bond_mappping).astype("string")
106
+
107
+ # Em 11/06/2024 o BC passou a informar nas colunas de cotacao os valores dos PUs
108
+ # Isso afeta somente os títulos LFT e NTN-B
109
+ change_Date = pd.Timestamp("2024-06-11")
110
+ adjusted_price = (df["ValueFR"] / df["AcceptedQuantityFR"]).round(6)
111
+ is_after_change_Date = df["Date"] >= change_Date
112
+ is_ltn_or_ntnf = df["BondType"].isin(["LTN", "NTN-F"])
113
+ keep_avg_price = is_after_change_Date | is_ltn_or_ntnf
114
+ df["AvgPrice"] = df["AvgPrice"].where(keep_avg_price, adjusted_price)
115
+
116
+ return df
117
+
118
+
119
+ def _adjust_null_values(df: pd.DataFrame) -> pd.DataFrame:
120
+ # Onde não há quantidade aceita, não há nem taxa e nem PU definidos.
121
+ is_accepted = df["AcceptedQuantityFR"] != 0
122
+ df["AvgRate"] = df["AvgRate"].where(is_accepted, pd.NA)
123
+ df["CutRate"] = df["CutRate"].where(is_accepted, pd.NA)
124
+ df["AvgPrice"] = df["AvgPrice"].where(is_accepted, pd.NA)
125
+ df["CutPrice"] = df["CutPrice"].where(is_accepted, pd.NA)
126
+
127
+ return df
128
+
129
+
130
+ def _add_dv01(df: pd.DataFrame) -> pd.DataFrame:
131
+ df["BDToMat"] = yd.bday.count(start=df["Date"], end=df["Maturity"])
132
+
133
+ is_accepted = df["AcceptedQuantityFR"] != 0 # noqa
134
+ df_not_accepted = df.query("~@is_accepted").reset_index(drop=True)
135
+ df_is_accepted = df.query("@is_accepted").reset_index(drop=True)
136
+
137
+ df_lft = df_is_accepted.query("BondType == 'LFT'").reset_index(drop=True)
138
+
139
+ df_ltn = df_is_accepted.query("BondType == 'LTN'").reset_index(drop=True)
140
+ if not df_ltn.empty:
141
+ df_ltn["Duration"] = df_ltn["BDToMat"] / 252
142
+ df_ltn["MDuration"] = df_ltn["Duration"] / (1 + df_ltn["AvgRate"])
143
+ df_ltn["DV01FR"] = 0.0001 * df_ltn["MDuration"] * df_ltn["AvgPrice"]
144
+
145
+ def compute_f_duration(row):
146
+ return duration_f(row["Date"], row["Maturity"], row["CutRate"])
147
+
148
+ df_ntnf = df_is_accepted.query("BondType == 'NTN-F'").reset_index(drop=True)
149
+ if not df_ntnf.empty:
150
+ df_ntnf["Duration"] = df_ntnf.apply(compute_f_duration, axis=1)
151
+ df_ntnf["Duration"] = df_ntnf["Duration"].astype("Float64")
152
+ df_ntnf["MDuration"] = df_ntnf["Duration"] / (1 + df_ntnf["AvgRate"])
153
+ df_ntnf["DV01FR"] = 0.0001 * df_ntnf["MDuration"] * df_ntnf["AvgPrice"]
154
+
155
+ def compute_b_duration(row):
156
+ return duration_b(row["Date"], row["Maturity"], row["CutRate"])
157
+
158
+ df_ntnb = df_is_accepted.query("BondType == 'NTN-B'").reset_index(drop=True)
159
+ if not df_ntnb.empty:
160
+ df_ntnb["Duration"] = df_ntnb.apply(compute_b_duration, axis=1)
161
+ df_ntnb["Duration"] = df_ntnb["Duration"].astype("Float64")
162
+ df_ntnb["MDuration"] = df_ntnb["Duration"] / (1 + df_ntnb["AvgRate"])
163
+ df_ntnb["DV01FR"] = 0.0001 * df_ntnb["MDuration"] * df_ntnb["AvgPrice"]
164
+
165
+ df = pd.concat([df_not_accepted, df_lft, df_ltn, df_ntnf, df_ntnb])
166
+
167
+ df["DV01FR"] *= df["AcceptedQuantityFR"]
168
+ df["DV01FR"] = df["DV01FR"].round(0).astype("Int64")
169
+
170
+ return df
171
+
172
+
173
+ def _filter_auction_by_type(
174
+ df: pd.DataFrame, auction_type: Literal["Sell", "Buy"]
175
+ ) -> pd.DataFrame:
176
+ auction_type_mapping = {"Venda": "Sell", "Compra": "Buy"}
177
+ auction_type = auction_type_mapping.get(auction_type)
178
+ if auction_type:
179
+ df = df.query(f"AuctionType == '{auction_type}'").reset_index(drop=True)
180
+ return df
181
+
182
+
183
+ def _sort_and_reorder_columns(df: pd.DataFrame) -> pd.DataFrame:
184
+ column_sequence = [
185
+ "Date",
186
+ "Settlement",
187
+ "AuctionType",
188
+ "Ordinance",
189
+ "Buyer",
190
+ "BondType",
191
+ "Maturity",
192
+ "SelicCode",
193
+ "Value",
194
+ "ValueFR",
195
+ "ValueSR",
196
+ "OfferedQuantity",
197
+ "OfferedQuantityFR",
198
+ "OfferedQuantitySR",
199
+ "AcceptedQuantity",
200
+ "AcceptedQuantityFR",
201
+ "AcceptedQuantitySR",
202
+ "AvgPrice",
203
+ "CutPrice",
204
+ "AvgRate",
205
+ "CutRate",
206
+ "BDToMat",
207
+ "Duration",
208
+ "MDuration",
209
+ "DV01FR",
210
+ ]
211
+
212
+ primary_sort_keys = ["Date", "AuctionType", "BondType", "Maturity"]
213
+ return df[column_sequence].sort_values(by=primary_sort_keys).reset_index(drop=True)
214
+
215
+
216
+ def _fetch_df_from_url(
217
+ url: str,
218
+ auction_type: Literal["Sell", "Buy"] = None,
219
+ ) -> pd.DataFrame:
220
+ try:
221
+ df = _load_from_url(url)
222
+ df = _pre_process_df(df)
223
+ df = _adjust_null_values(df)
224
+ df = _process_df(df)
225
+ df = _add_dv01(df)
226
+ df = _filter_auction_by_type(df, auction_type)
227
+ df = _sort_and_reorder_columns(df)
228
+ return df
229
+ except Exception:
230
+ logging.exception("Error fetching auction data from BC API.")
231
+ return pd.DataFrame()
232
+
233
+
234
+ def get_all_auctions() -> pd.DataFrame:
235
+ """
236
+ Retrieves a DataFrame containing all public auction data from the BC.
237
+
238
+ This function fetches the complete series of public auctions conducted by the
239
+ National Treasury via the BC API and processes it to a standardized DataFrame.
240
+
241
+ Returns:
242
+ pd.DataFrame: A DataFrame sorted by Date, AuctionType, BondType, and Maturity
243
+ containing auction data. In case of an error during data retrieval, an empty
244
+ DataFrame is returned and an error message is logged.
245
+ """
246
+ # URL com toda a série de leilões de títulos públicos do Tesouro Nacional
247
+ url = f"{BASE_API_URL}$format=text/csv"
248
+
249
+ return _fetch_df_from_url(url)
250
+
251
+
252
+ def auctions(
253
+ start: DateScalar,
254
+ end: DateScalar | None = None,
255
+ auction_type: Literal["Sell", "Buy"] = None,
256
+ ) -> pd.DataFrame:
257
+ """
258
+ Retrieves auction data for a given date range and auction type.
259
+
260
+ It fetches auction data from the BC API for the specified start and end dates.
261
+ If the end date is not provided, it defaults to the start date.
262
+ The resulting data is then filtered by the specified auction type ("Sell" or "Buy").
263
+ "Sell" auctions are those where the National Treasury sells bonds to the market.
264
+ "Buy" auctions are those where the National Treasury buys bonds from the market.
265
+
266
+ Args:
267
+ start (DateScalar): The start date for the auction data query.
268
+ end (DateScalar | None, optional): The end date for the auction data query.
269
+ If None, the start date is used. Defaults to None.
270
+ auction_type (Literal["Sell", "Buy"], optional): The type of auction to filter
271
+ by. Defaults to "Sell".
272
+
273
+ Returns:
274
+ pd.DataFrame: A DataFrame containing auction data for the specified date range
275
+ and auction type. In case of an error during data retrieval, an empty
276
+ DataFrame is returned and an error message is logged.
277
+
278
+ Notes:
279
+ FR = First Round
280
+ SR = Second Round
281
+
282
+ The DataFrame has the following columns:
283
+ - Date: Date of the auction.
284
+ - Settlement: Settlement date of the auction.
285
+ - AuctionType: Type of auction (e.g., "Sell" or "Buy").
286
+ - Ordinance: Normative ordinance associated with the auction.
287
+ - Buyer: Buyer category (e.g., "TodoMercado", "SomenteDealerApto").
288
+ - BondType: Category of the bond (e.g., "LTN", "LFT", "NTN-B", "NTN-F").
289
+ - Maturity: Bond maturity date.
290
+ - SelicCode: Bond code in the Selic system.
291
+ - Value: Total value of the auction in R$ (FR + SR).
292
+ - ValueFR: First round (FR) value of the auction in R$.
293
+ - ValueSR: Value in the second round (SR) in R$.
294
+ - OfferedQuantity: Total quantity offered in the auction (FR + SR).
295
+ - OfferedQuantityFR: Quantity offered in the first round (FR).
296
+ - OfferedQuantitySR: Quantity offered in the second round (SR).
297
+ - AcceptedQuantity: Total quantity accepted in the auction (FR + SR).
298
+ - AcceptedQuantityFR: Quantity accepted in the first round (FR).
299
+ - AcceptedQuantitySR: Quantity accepted in the second round (SR).
300
+ - AvgPrice: Average price in the auction.
301
+ - CutPrice: Cut-off price.
302
+ - AvgRate: Average interest rate.
303
+ - CutRate: Cut-off rate.
304
+ - BDToMat: Business days to maturity.
305
+ - Duration: Duration of the bond.
306
+ - MDuration: Modified duration of the bond.
307
+ - DV01FR: First Round (FR) dollar value of a 1 bp change in yield in R$.
308
+
309
+ """
310
+
311
+ start = dc.convert_input_dates(start)
312
+ if end:
313
+ end = dc.convert_input_dates(end)
314
+ else:
315
+ end = start
316
+
317
+ start_str = start.strftime("%Y-%m-%d")
318
+ end_str = end.strftime("%Y-%m-%d")
319
+ # 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"
320
+ url = f"{BASE_API_URL}@dataMovimentoInicio='{start_str}'&@dataMovimentoFim='{end_str}'&$format=text/csv" # noqa
321
+
322
+ return _fetch_df_from_url(url, auction_type)
@@ -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.1"
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
File without changes