PYield 0.35.2__tar.gz → 0.35.4__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 (53) hide show
  1. {pyield-0.35.2 → pyield-0.35.4}/.gitignore +0 -6
  2. {pyield-0.35.2 → pyield-0.35.4}/PKG-INFO +1 -1
  3. pyield-0.35.4/pyield/__about__.py +1 -0
  4. {pyield-0.35.2 → pyield-0.35.4}/pyield/__init__.py +6 -5
  5. {pyield-0.35.2 → pyield-0.35.4}/pyield/b3/di1.py +2 -2
  6. {pyield-0.35.2 → pyield-0.35.4}/pyield/bc/auction.py +72 -63
  7. {pyield-0.35.2 → pyield-0.35.4}/pyield/bc/trades_intraday.py +12 -1
  8. {pyield-0.35.2 → pyield-0.35.4}/pyield/data_cache.py +5 -4
  9. pyield-0.35.4/pyield/tn/__init__.py +3 -0
  10. pyield-0.35.4/pyield/tn/auction.py +91 -0
  11. {pyield-0.35.2/pyield/tpf → pyield-0.35.4/pyield/tn}/lft.py +1 -1
  12. {pyield-0.35.2/pyield/tpf → pyield-0.35.4/pyield/tn}/ltn.py +1 -1
  13. {pyield-0.35.2/pyield/tpf → pyield-0.35.4/pyield/tn}/ntnb.py +9 -3
  14. {pyield-0.35.2/pyield/tpf → pyield-0.35.4/pyield/tn}/ntnc.py +9 -3
  15. {pyield-0.35.2/pyield/tpf → pyield-0.35.4/pyield/tn}/ntnf.py +9 -3
  16. pyield-0.35.2/pyield/__about__.py +0 -1
  17. pyield-0.35.2/pyield/tpf/__init__.py +0 -0
  18. {pyield-0.35.2 → pyield-0.35.4}/LICENSE +0 -0
  19. {pyield-0.35.2 → pyield-0.35.4}/README.md +0 -0
  20. {pyield-0.35.2 → pyield-0.35.4}/pyield/anbima/__init__.py +0 -0
  21. {pyield-0.35.2 → pyield-0.35.4}/pyield/anbima/ettj.py +0 -0
  22. {pyield-0.35.2 → pyield-0.35.4}/pyield/anbima/ima.py +0 -0
  23. {pyield-0.35.2 → pyield-0.35.4}/pyield/anbima/imaq.py +0 -0
  24. {pyield-0.35.2 → pyield-0.35.4}/pyield/anbima/ipca.py +0 -0
  25. {pyield-0.35.2 → pyield-0.35.4}/pyield/anbima/tpf.py +0 -0
  26. {pyield-0.35.2 → pyield-0.35.4}/pyield/b3/__init__.py +0 -0
  27. {pyield-0.35.2 → pyield-0.35.4}/pyield/b3/di_over.py +0 -0
  28. {pyield-0.35.2 → pyield-0.35.4}/pyield/b3/futures/__init__.py +0 -0
  29. {pyield-0.35.2 → pyield-0.35.4}/pyield/b3/futures/common.py +0 -0
  30. {pyield-0.35.2 → pyield-0.35.4}/pyield/b3/futures/historical.py +0 -0
  31. {pyield-0.35.2 → pyield-0.35.4}/pyield/b3/futures/intraday.py +0 -0
  32. {pyield-0.35.2 → pyield-0.35.4}/pyield/b3/futures/xml.py +0 -0
  33. {pyield-0.35.2 → pyield-0.35.4}/pyield/bc/__init__.py +0 -0
  34. {pyield-0.35.2 → pyield-0.35.4}/pyield/bc/bcdata.py +0 -0
  35. {pyield-0.35.2 → pyield-0.35.4}/pyield/bc/ptax.py +0 -0
  36. {pyield-0.35.2 → pyield-0.35.4}/pyield/bc/repo.py +0 -0
  37. {pyield-0.35.2 → pyield-0.35.4}/pyield/bc/trades_monthly.py +0 -0
  38. {pyield-0.35.2 → pyield-0.35.4}/pyield/bc/vna.py +0 -0
  39. {pyield-0.35.2 → pyield-0.35.4}/pyield/bday.py +0 -0
  40. {pyield-0.35.2 → pyield-0.35.4}/pyield/conftest.py +0 -0
  41. {pyield-0.35.2 → pyield-0.35.4}/pyield/date_converter.py +0 -0
  42. {pyield-0.35.2 → pyield-0.35.4}/pyield/fwd.py +0 -0
  43. {pyield-0.35.2 → pyield-0.35.4}/pyield/holidays/__init__.py +0 -0
  44. {pyield-0.35.2 → pyield-0.35.4}/pyield/holidays/br_holidays_new.txt +0 -0
  45. {pyield-0.35.2 → pyield-0.35.4}/pyield/holidays/br_holidays_old.txt +0 -0
  46. {pyield-0.35.2 → pyield-0.35.4}/pyield/holidays/brholidays.py +0 -0
  47. {pyield-0.35.2 → pyield-0.35.4}/pyield/ibge/__init__.py +0 -0
  48. {pyield-0.35.2 → pyield-0.35.4}/pyield/ibge/ipca.py +0 -0
  49. {pyield-0.35.2 → pyield-0.35.4}/pyield/interpolator.py +0 -0
  50. {pyield-0.35.2 → pyield-0.35.4}/pyield/py.typed +0 -0
  51. {pyield-0.35.2 → pyield-0.35.4}/pyield/retry.py +0 -0
  52. {pyield-0.35.2/pyield/tpf → pyield-0.35.4/pyield/tn}/tools.py +0 -0
  53. {pyield-0.35.2 → pyield-0.35.4}/pyproject.toml +0 -0
@@ -21,10 +21,6 @@ share/python-wheels/
21
21
  *.egg
22
22
  MANIFEST
23
23
 
24
- # pdm
25
- pdm.lock
26
- .pdm-python
27
-
28
24
  # uv
29
25
  uv.lock
30
26
 
@@ -34,8 +30,6 @@ uv.lock
34
30
  env/
35
31
  venv/
36
32
  ENV/
37
- env.bak/
38
- venv.bak/
39
33
 
40
34
  # Jupiter Notebook
41
35
  .ipynb_checkpoints
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PYield
3
- Version: 0.35.2
3
+ Version: 0.35.4
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.35.4"
@@ -1,29 +1,30 @@
1
1
  import logging
2
2
 
3
- from pyield import anbima, b3, bc, bday, ibge
3
+ from pyield import anbima, b3, bc, bday, ibge, tn
4
4
  from pyield.__about__ import __version__
5
5
  from pyield.b3 import di1, futures
6
6
  from pyield.fwd import forward, forwards
7
7
  from pyield.interpolator import Interpolator
8
- from pyield.tpf import lft, ltn, ntnb, ntnc, ntnf
8
+ from pyield.tn import lft, ltn, ntnb, ntnc, ntnf
9
9
 
10
10
  __all__ = [
11
11
  "__version__",
12
12
  "anbima",
13
+ "bc",
14
+ "b3",
15
+ "ibge",
16
+ "tn",
13
17
  "bday",
14
18
  "di1",
15
19
  "forwards",
16
20
  "forward",
17
21
  "futures",
18
- "ibge",
19
22
  "Interpolator",
20
23
  "lft",
21
24
  "ltn",
22
25
  "ntnb",
23
26
  "ntnc",
24
27
  "ntnf",
25
- "bc",
26
- "b3",
27
28
  ]
28
29
 
29
30
  # Configura o logger do pacote principal com um NullHandler
@@ -61,7 +61,7 @@ def data(
61
61
  # Remove duplicates and sort dates
62
62
  dates = dates.drop_duplicates().sort_values().reset_index(drop=True)
63
63
 
64
- df = get_cached_dataset("DI").query("TradeDate in @dates").reset_index(drop=True)
64
+ df = get_cached_dataset("di1").query("TradeDate in @dates").reset_index(drop=True)
65
65
 
66
66
  today = dt.now(TIMEZONE_BZ)
67
67
  last_bday = bday.last_business_day()
@@ -383,7 +383,7 @@ def eod_dates() -> pd.Series:
383
383
  dtype: datetime64[ns]
384
384
  """
385
385
  available_dates = (
386
- get_cached_dataset("DI")
386
+ get_cached_dataset("di1")
387
387
  .drop_duplicates(subset=["TradeDate"])["TradeDate"]
388
388
  .sort_values(ascending=True)
389
389
  .reset_index(drop=True)
@@ -16,8 +16,8 @@ from pyield import bday
16
16
  from pyield import date_converter as dc
17
17
  from pyield.date_converter import DateScalar
18
18
  from pyield.retry import default_retry
19
- from pyield.tpf.ntnb import duration as duration_b
20
- from pyield.tpf.ntnf import duration as duration_f
19
+ from pyield.tn.ntnb import duration as duration_b
20
+ from pyield.tn.ntnf import duration as duration_f
21
21
 
22
22
  """
23
23
  Dicionário com o mapeamento das colunas da API do BC para o DataFrame final
@@ -72,18 +72,17 @@ def _pre_process_df(df: pd.DataFrame) -> pd.DataFrame:
72
72
 
73
73
 
74
74
  def _process_df(df: pd.DataFrame) -> pd.DataFrame:
75
- """
76
- A API retorna somente os valores de SR e FR
77
- Assim, vamos ter que somar SR com FR para obter os valores totais
78
- Por isso, vamos remover valores nulos de SR e FR para o nulo não ser propagado
79
- """
75
+ # A API retorna somente os valores de SR e FR
76
+ # Assim, vamos ter que somar SR com FR para obter os valores totais
77
+
78
+ # Remover valores nulos de SR e FR para o nulo não ser propagado
80
79
  df["AcceptedQuantitySR"] = df["AcceptedQuantitySR"].fillna(0)
81
80
  df["OfferedQuantitySR"] = df["OfferedQuantitySR"].fillna(0)
82
81
 
83
82
  df["OfferedQuantity"] = df["OfferedQuantityFR"] + df["OfferedQuantitySR"]
84
83
  df["AcceptedQuantity"] = df["AcceptedQuantityFR"] + df["AcceptedQuantitySR"]
85
- # Total value of the auction in R$ millions -> convert it to unit value
86
- df["Value"] = (1_000_000 * df["Value"]).round(0).astype("Int64")
84
+ # Total value of the auction in R$ millions -> convert to R$
85
+ df["Value"] = (1_000_000 * df["Value"]).round(2)
87
86
 
88
87
  # Remove the percentage sign and round to 6 decimal places (4 decimal places in %)
89
88
  # Before 25/08/2015, there were no rounding rules for the rates in the BC API
@@ -96,12 +95,12 @@ def _process_df(df: pd.DataFrame) -> pd.DataFrame:
96
95
  first_round_ratio = first_round_ratio.where(df["AcceptedQuantityFR"] != 0, 0)
97
96
  # O dado do financeiro do BC está em milhões com uma casa decimal de precisão
98
97
  # Portanto, podemos converter para inteiro sem perda de informação
99
- df["ValueFR"] = (first_round_ratio * df["Value"]).round(0).astype("Int64")
98
+ df["ValueFR"] = (first_round_ratio * df["Value"]).round(2)
100
99
 
101
100
  # Calculate the financial value of the second round
102
101
  second_round_ratio = df["AcceptedQuantitySR"] / df["AcceptedQuantity"]
103
102
  second_round_ratio = second_round_ratio.where(df["AcceptedQuantitySR"] != 0, 0)
104
- df["ValueSR"] = (second_round_ratio * df["Value"]).round(0).astype("Int64")
103
+ df["ValueSR"] = (second_round_ratio * df["Value"]).round(2)
105
104
 
106
105
  bond_mappping = {
107
106
  100000: "LTN",
@@ -114,9 +113,9 @@ def _process_df(df: pd.DataFrame) -> pd.DataFrame:
114
113
 
115
114
  # Em 11/06/2024 o BC passou a informar nas colunas de cotacao os valores dos PUs
116
115
  # Isso afeta somente os títulos LFT e NTN-B
117
- change_Date = pd.Timestamp("2024-06-11")
116
+ change_date = pd.Timestamp("2024-06-11")
118
117
  adjusted_price = (df["ValueFR"] / df["AcceptedQuantityFR"]).round(6)
119
- is_after_change_Date = df["Date"] >= change_Date
118
+ is_after_change_Date = df["Date"] >= change_date
120
119
  is_ltn_or_ntnf = df["BondType"].isin(["LTN", "NTN-F"])
121
120
  keep_avg_price = is_after_change_Date | is_ltn_or_ntnf
122
121
  df["AvgPrice"] = df["AvgPrice"].where(keep_avg_price, adjusted_price)
@@ -128,63 +127,69 @@ def _process_df(df: pd.DataFrame) -> pd.DataFrame:
128
127
 
129
128
 
130
129
  def _adjust_null_values(df: pd.DataFrame) -> pd.DataFrame:
131
- # Onde não há quantidade aceita, não há nem taxa e nem PU definidos.
130
+ # Onde não há quantidade aceita na primeira volta, não há taxa ou PU definidos.
132
131
  is_accepted = df["AcceptedQuantityFR"] != 0
133
- df["AvgRate"] = df["AvgRate"].where(is_accepted, pd.NA)
134
- df["CutRate"] = df["CutRate"].where(is_accepted, pd.NA)
135
- df["AvgPrice"] = df["AvgPrice"].where(is_accepted, pd.NA)
136
- df["CutPrice"] = df["CutPrice"].where(is_accepted, pd.NA)
132
+ cols_to_update = ["AvgRate", "CutRate", "AvgPrice", "CutPrice"]
133
+ df[cols_to_update] = df[cols_to_update].where(is_accepted, pd.NA)
137
134
 
138
135
  return df
139
136
 
140
137
 
141
- def _add_dv01(df: pd.DataFrame) -> pd.DataFrame:
142
- # DV01 irá armazenar o valor do DV01 por título até o final da função
143
- # Ao final, o DV01 será multiplicado pela quantidade aceita
144
- df["DV01"] = 0
145
- is_accepted = df["AcceptedQuantity"] != 0 # noqa
146
- df_not_accepted = df.query("~@is_accepted").reset_index(drop=True)
147
- df_accepted = df.query("@is_accepted").reset_index(drop=True)
138
+ def _add_duration(df: pd.DataFrame) -> pd.DataFrame:
139
+ df["Duration"] = 0.0
140
+ df_lft = df.query("BondType in ['LFT']").reset_index(drop=True)
148
141
 
149
- df_lft = df_accepted.query("BondType == 'LFT'").reset_index(drop=True)
150
-
151
- df_ltn = df_accepted.query("BondType == 'LTN'").reset_index(drop=True)
142
+ df_ltn = df.query("BondType == 'LTN'").reset_index(drop=True)
152
143
  if not df_ltn.empty:
153
144
  df_ltn["Duration"] = df_ltn["BDToMat"] / 252
154
- mduration = df_ltn["Duration"] / (1 + df_ltn["AvgRate"])
155
- df_ltn["DV01"] = 0.0001 * mduration * df_ltn["AvgPrice"]
156
-
157
- def compute_f_duration(row):
158
- return duration_f(row["Date"], row["Maturity"], row["CutRate"])
159
145
 
160
- df_ntnf = df_accepted.query("BondType == 'NTN-F'").reset_index(drop=True)
146
+ df_ntnf = df.query("BondType == 'NTN-F'").reset_index(drop=True)
161
147
  if not df_ntnf.empty:
162
- df_ntnf["Duration"] = df_ntnf.apply(compute_f_duration, axis=1)
163
- df_ntnf["Duration"] = df_ntnf["Duration"].astype("Float64")
164
- mduration = df_ntnf["Duration"] / (1 + df_ntnf["AvgRate"])
165
- df_ntnf["DV01"] = 0.0001 * mduration * df_ntnf["AvgPrice"]
166
-
167
- def compute_b_duration(row):
168
- return duration_b(row["Date"], row["Maturity"], row["CutRate"])
148
+ df_ntnf["Duration"] = df_ntnf.apply(
149
+ lambda row: duration_f(row["Settlement"], row["Maturity"], row["AvgRate"]),
150
+ axis=1,
151
+ )
169
152
 
170
- df_ntnb = df_accepted.query("BondType == 'NTN-B'").reset_index(drop=True)
153
+ df_ntnb = df.query("BondType == 'NTN-B'").reset_index(drop=True)
171
154
  if not df_ntnb.empty:
172
- df_ntnb["Duration"] = df_ntnb.apply(compute_b_duration, axis=1)
173
- df_ntnb["Duration"] = df_ntnb["Duration"].astype("Float64")
174
- mduration = df_ntnb["Duration"] / (1 + df_ntnb["AvgRate"])
175
- df_ntnb["DV01"] = 0.0001 * mduration * df_ntnb["AvgPrice"]
155
+ df_ntnb["Duration"] = df_ntnb.apply(
156
+ lambda row: duration_b(row["Settlement"], row["Maturity"], row["AvgRate"]),
157
+ axis=1,
158
+ )
176
159
 
177
- df = pd.concat([df_not_accepted, df_lft, df_ltn, df_ntnf, df_ntnb])
160
+ df = pd.concat([df_lft, df_ltn, df_ntnf, df_ntnb]).reset_index(drop=True)
161
+ df["Duration"] = df["Duration"].astype("Float64")
178
162
 
179
- df["DV01FR"] = df["DV01"] * df["AcceptedQuantityFR"]
180
- df["DV01FR"] = df["DV01FR"].round(0).astype("Int64")
163
+ return df
164
+
165
+
166
+ def _add_dv01(df: pd.DataFrame) -> pd.DataFrame:
167
+ # DV01 por título calculado com base nos valores da primeira rodada
168
+ mduration = df["Duration"] / (1 + df["AvgRate"])
169
+ dv01 = 0.0001 * mduration * df["AvgPrice"]
181
170
 
182
- df["DV01SR"] = df["DV01"] * df["AcceptedQuantitySR"]
183
- df["DV01SR"] = df["DV01SR"].round(0).astype("Int64")
171
+ # Valores totais de DV01 para o leilão
172
+ df["DV01"] = dv01 * df["AcceptedQuantity"]
173
+ df["DV01FR"] = dv01 * df["AcceptedQuantityFR"]
174
+ df["DV01SR"] = dv01 * df["AcceptedQuantitySR"]
184
175
 
185
- # DV01 irá armazenar o valor total do leilão
186
- df["DV01"] *= df["AcceptedQuantity"]
187
- df["DV01"] = df["DV01"].round(0).astype("Int64")
176
+ for col in ["DV01", "DV01FR", "DV01SR"]:
177
+ # Definiri DV01 nulos do leilão como 0
178
+ df[col] = df[col].fillna(0).round(2)
179
+
180
+ # Forçar 0 nas LFT, pois não há DV01
181
+ df.loc[df["BondType"] == "LFT", ["DV01", "DV01FR", "DV01SR"]] = 0.0
182
+
183
+ return df
184
+
185
+
186
+ def _add_avg_maturity(df: pd.DataFrame) -> pd.DataFrame:
187
+ # Na metodolgia do Tesouro Nacional, a maturidade média é a mesma que a duração
188
+ df["AvgMaturity"] = df["Duration"]
189
+
190
+ # Para LFT, a maturidade média é calculada como o n. de dias úteis até o vencimento
191
+ is_lft = df["BondType"] == "LFT"
192
+ df.loc[is_lft, "AvgMaturity"] = df.loc[is_lft, "BDToMat"] / 252
188
193
 
189
194
  return df
190
195
 
@@ -197,10 +202,11 @@ def _sort_and_reorder_columns(df: pd.DataFrame) -> pd.DataFrame:
197
202
  "Ordinance",
198
203
  "Buyer",
199
204
  "BondType",
205
+ "SelicCode",
200
206
  "Maturity",
201
207
  "BDToMat",
202
208
  "Duration",
203
- "SelicCode",
209
+ "AvgMaturity",
204
210
  "Value",
205
211
  "ValueFR",
206
212
  "ValueSR",
@@ -232,7 +238,9 @@ def _fetch_df_from_url(url: str) -> pd.DataFrame:
232
238
  df = _pre_process_df(df)
233
239
  df = _adjust_null_values(df)
234
240
  df = _process_df(df)
241
+ df = _add_duration(df)
235
242
  df = _add_dv01(df)
243
+ df = _add_avg_maturity(df)
236
244
  df = _sort_and_reorder_columns(df)
237
245
  return df
238
246
  except Exception:
@@ -243,7 +251,7 @@ def _fetch_df_from_url(url: str) -> pd.DataFrame:
243
251
  def auctions(
244
252
  start: DateScalar | None = None,
245
253
  end: DateScalar | None = None,
246
- auction_type: Literal["Sell", "Buy"] | None = None,
254
+ auction_type: Literal["sell", "buy"] | None = None,
247
255
  ) -> pd.DataFrame:
248
256
  """
249
257
  Recupera dados de leilões para um determinado período e tipo de leilão da API do BC.
@@ -286,7 +294,7 @@ def auctions(
286
294
  leilão desde a data mais antiga disponível até a data de `end`.
287
295
  Se `start` e `end` forem `None`, a série histórica completa será retornada.
288
296
  Padrão é `None`.
289
- auction_type (Literal["Sell", "Buy"], opcional): O tipo de leilão para filtrar
297
+ auction_type (Literal["sell", "buy"], opcional): O tipo de leilão para filtrar
290
298
  diretamente na API. Padrão é `None` (retorna todos os tipos de leilão).
291
299
 
292
300
  Returns:
@@ -305,8 +313,12 @@ def auctions(
305
313
  - Ordinance: Edital normativo associado ao leilão.
306
314
  - Buyer: Categoria do comprador (ex: "TodoMercado", "SomenteDealerApto").
307
315
  - BondType: Categoria do título (ex: "LTN", "LFT", "NTN-B", "NTN-F").
308
- - Maturity: Data de vencimento do título.
309
316
  - SelicCode: Código do título no sistema Selic.
317
+ - Maturity: Data de vencimento do título.
318
+ - DV01: Valor do DV01 total do leilão em R$.
319
+ - DV01FR: DV01 da Primeira Rodada (FR) em R$.
320
+ - DV01SR: DV01 da Segunda Rodada (SR) em R$.
321
+ - AvgMaturity: Maturidade média do título (em anos).
310
322
  - Value: Valor total do leilão em R$ (FR + SR).
311
323
  - ValueFR: Valor da primeira rodada (FR) do leilão em R$.
312
324
  - ValueSR: Valor da segunda rodada (SR) em R$.
@@ -322,9 +334,6 @@ def auctions(
322
334
  - CutRate: Taxa de corte.
323
335
  - BDToMat: Dias úteis até o vencimento.
324
336
  - Duration: Duration (Duração) do título.
325
- - DV01: Valor do DV01 total do leilão em R$.
326
- - DV01FR: DV01 da Primeira Rodada (FR) em R$.
327
- - DV01SR: DV01 da Segunda Rodada (SR) em R$.
328
337
  """
329
338
  url = BASE_API_URL
330
339
  if start:
@@ -338,8 +347,8 @@ def auctions(
338
347
  url += f"&@dataMovimentoFim='{end_str}'"
339
348
 
340
349
  # Mapeamento do auction_type para o valor esperado pela API
341
- auction_type_mapping = {"Sell": "Venda", "Buy": "Compra"}
342
- auction_type_api_value = auction_type_mapping.get(str(auction_type))
350
+ auction_type_mapping = {"sell": "Venda", "buy": "Compra"}
351
+ auction_type_api_value = auction_type_mapping.get(str(auction_type.lower()))
343
352
  # Adiciona o parâmetro tipoOferta à URL se auction_type for fornecido
344
353
  if auction_type_api_value:
345
354
  url += f"&@tipoOferta='{auction_type_api_value}'"
@@ -1,3 +1,8 @@
1
+ """
2
+ Fetches real-time secondary trading data for domestic Federal Public Debt (FPD)
3
+ https://www.bcb.gov.br/htms/selic/selicprecos.asp?frame=1
4
+ """
5
+
1
6
  import datetime as dt
2
7
  import io
3
8
  import logging
@@ -18,7 +23,13 @@ test_file = Path(__file__).parent.parent.parent / "tests/data/negocios-registrad
18
23
 
19
24
 
20
25
  def _fetch_csv_from_url() -> str:
21
- FILE_URL = "https://www3.bcb.gov.br/novoselic/NegociosRegistradosDownload"
26
+ """
27
+ Example URL for the CSV file containing intraday trades data:
28
+ https://www3.bcb.gov.br/novoselic/rest/precosNegociacao/pub/download/estatisticas/02-06-2025
29
+ """
30
+ today = dt.datetime.now(BZ_TIMEZONE).date()
31
+ formatted_date = today.strftime("%d-%m-%Y")
32
+ FILE_URL = f"https://www3.bcb.gov.br/novoselic/rest/precosNegociacao/pub/download/estatisticas/{formatted_date}"
22
33
  r = requests.get(FILE_URL, timeout=30) # API usually takes 10s to respond
23
34
  r.raise_for_status()
24
35
  r.encoding = "iso-8859-15"
@@ -1,6 +1,7 @@
1
1
  import functools
2
2
  import logging
3
3
  from datetime import datetime
4
+ from typing import Literal
4
5
  from zoneinfo import ZoneInfo
5
6
 
6
7
  import pandas as pd
@@ -16,12 +17,12 @@ BASE_DATA_URL = f"{GIT_URL}/data"
16
17
 
17
18
  # Dicionário global para configurações dos datasets usando 'filename'
18
19
  DATASET_CONFIGS = {
19
- "DI": {
20
+ "di1": {
20
21
  "filename": "b3_di.parquet", # Apenas o nome do arquivo
21
22
  "date_column": "TradeDate",
22
23
  "name": "Futuro de DI (B3)",
23
24
  },
24
- "TPF": {
25
+ "tpf": {
25
26
  "filename": "anbima_tpf.parquet", # Apenas o nome do arquivo
26
27
  "date_column": "ReferenceDate",
27
28
  "name": "TPF (ANBIMA)",
@@ -73,6 +74,6 @@ def _get_dataset_with_ttl(dataset_id: str, date_key: str) -> pd.DataFrame:
73
74
  raise # Re-raise a exceção para que o chamador saiba que falhou
74
75
 
75
76
 
76
- def get_cached_dataset(dataset_id: str) -> pd.DataFrame:
77
+ def get_cached_dataset(dataset_id: Literal["di1", "tpf"]) -> pd.DataFrame:
77
78
  """Obtém um dataset configurado pelo seu ID. O cache expira diariamente."""
78
- return _get_dataset_with_ttl(dataset_id, _get_today_date_key())
79
+ return _get_dataset_with_ttl(dataset_id.lower(), _get_today_date_key())
@@ -0,0 +1,3 @@
1
+ from pyield.tn.auction import benchmarks
2
+
3
+ __all__ = ["benchmarks"]
@@ -0,0 +1,91 @@
1
+ """
2
+ Manual da API: https://portal-conhecimento.tesouro.gov.br/catalogo-componentes/api-leil%C3%B5es
3
+ Financeiro é sempre o financeiro aceito.
4
+ Os dados do BCB só existem na API do TN.
5
+ """
6
+
7
+ import logging
8
+
9
+ import pandas as pd
10
+ import requests
11
+
12
+ BENCHMARKS_COLUMN_MAPPING = {
13
+ "BENCHMARK": "Benchmark",
14
+ "VENCIMENTO": "MaturityDate",
15
+ "TÍTULO": "BondType",
16
+ "INÍCIO": "StartDate",
17
+ "TERMINO": "EndDate",
18
+ }
19
+
20
+
21
+ API_URL = "https://apiapex.tesouro.gov.br/aria/v1/api-leiloes-pub/custom/benchmarks?incluir_historico="
22
+
23
+
24
+ def benchmarks(include_history: bool = False) -> pd.DataFrame:
25
+ """Fetches benchmark data for Brazilian Treasury Bonds from the TN API.
26
+
27
+ This function retrieves current or historical benchmark data for various Brazilian
28
+ Treasury bond types (e.g., LTN, LFT, NTN-B). The data is sourced directly from the
29
+ official Tesouro Nacional API.
30
+
31
+ Args:
32
+ include_history (bool, optional): If `True`, includes historical benchmark data.
33
+ If `False` (default), only current benchmarks are returned.
34
+
35
+ Returns:
36
+ pd.DataFrame: A pandas DataFrame containing the benchmark data.
37
+ The DataFrame includes the following columns:
38
+
39
+ * `Benchmark` (str): The name or identifier of the benchmark
40
+ (e.g., 'LFT 3 anos').
41
+ * `MaturityDate` (datetime64[ns]): The maturity date of the benchmark.
42
+ * `BondType` (str): The type of the bond (e.g., 'LTN', 'LFT', 'NTN-B').
43
+ * `StartDate` (datetime64[ns]): The start date for the benchmark's period.
44
+ * `EndDate` (datetime64[ns]): The end date for the benchmark's period.
45
+
46
+ Notes:
47
+ * Data is sourced from the official Tesouro Nacional (Brazilian Treasury) API.
48
+ * An retry mechanism is implemented for SSL certificate verification errors.
49
+ * The API documentation can be found at:
50
+ https://portal-conhecimento.tesouro.gov.br/catalogo-componentes/api-leil%C3%B5es
51
+ * Rows with any `NaN` values are dropped before returning the DataFrame.
52
+
53
+ Examples:
54
+ >>> # Get current benchmarks
55
+ >>> from pyield import tn
56
+ >>> df_current = tn.benchmarks()
57
+
58
+ >>> # Get historical benchmarks
59
+ >>> df_history = benchmarks(include_history=True)
60
+ >>> df_history.head()
61
+ Benchmark MaturityDate BondType StartDate EndDate
62
+ 0 LFT 6 anos 2020-03-01 LFT 2014-01-01 2014-06-30
63
+ 1 LFT 6 anos 2020-09-01 LFT 2014-07-01 2014-12-31
64
+ 2 LFT 6 anos 2021-03-01 LFT 2015-01-01 2015-04-30
65
+ 3 LFT 6 anos 2021-09-01 LFT 2015-05-01 2015-12-31
66
+ 4 LFT 1 ano 2022-03-01 LFT 2020-10-15 2021-03-31
67
+ """
68
+ session = requests.Session()
69
+ include_history_param = "S" if include_history else "N"
70
+ api_endpoint = API_URL + include_history_param
71
+ try:
72
+ stn_benchmarks = session.get(api_endpoint)
73
+ except requests.exceptions.SSLError as e:
74
+ logging.error(
75
+ f"SSL error encountered: {e}. Retrying without certificate verification."
76
+ )
77
+ stn_benchmarks = session.get(api_endpoint, verify=False)
78
+ response_dict = stn_benchmarks.json()
79
+ df = pd.DataFrame(response_dict["registros"]).dropna()
80
+ df = df.convert_dtypes()
81
+ # A API pode retornar vazio, então é necessário verificar
82
+ if not df.empty:
83
+ df["VENCIMENTO"] = pd.to_datetime(df["VENCIMENTO"])
84
+ df["TERMINO"] = pd.to_datetime(df["TERMINO"])
85
+ df["INÍCIO"] = pd.to_datetime(df["INÍCIO"])
86
+ df["BENCHMARK"] = df["BENCHMARK"].str.strip()
87
+ df["TÍTULO"] = df["TÍTULO"].str.strip()
88
+ else:
89
+ logging.warning("Não foi possível obter os benchmarks do STN.")
90
+
91
+ return df.rename(columns=BENCHMARKS_COLUMN_MAPPING)
@@ -3,7 +3,7 @@ import pandas as pd
3
3
  from pyield import anbima, bday
4
4
  from pyield import date_converter as dc
5
5
  from pyield.date_converter import DateScalar
6
- from pyield.tpf import tools
6
+ from pyield.tn import tools
7
7
 
8
8
 
9
9
  def data(date: DateScalar) -> pd.DataFrame:
@@ -4,7 +4,7 @@ from pyield import anbima, bday
4
4
  from pyield import date_converter as dc
5
5
  from pyield.b3 import di1
6
6
  from pyield.date_converter import DateScalar
7
- from pyield.tpf import tools
7
+ from pyield.tn import tools
8
8
 
9
9
  FACE_VALUE = 1000
10
10
 
@@ -3,7 +3,7 @@ import pandas as pd
3
3
 
4
4
  import pyield.date_converter as dc
5
5
  import pyield.interpolator as ip
6
- import pyield.tpf.tools as bt
6
+ import pyield.tn.tools as bt
7
7
  from pyield import anbima, bday
8
8
  from pyield.date_converter import DateScalar
9
9
 
@@ -541,14 +541,20 @@ def duration(
541
541
  >>> ntnb.duration("23-08-2024", "15-08-2060", 0.061005)
542
542
  15.083054313130464
543
543
  """
544
+ # Return NaN if any input is NaN
545
+ if any(pd.isna(x) for x in [settlement, maturity, rate]):
546
+ return float("NaN")
547
+
548
+ # Validate and normalize dates
544
549
  settlement = dc.convert_input_dates(settlement)
545
550
  maturity = dc.convert_input_dates(maturity)
546
551
 
547
552
  df = cash_flows(settlement, maturity)
548
553
  df["BY"] = bday.count(settlement, df["PaymentDate"]) / 252
549
554
  df["DCF"] = df["CashFlow"] / (1 + rate) ** df["BY"]
550
- np_duration = (df["DCF"] * df["BY"]).sum() / df["DCF"].sum()
551
- return float(np_duration)
555
+ duration = (df["DCF"] * df["BY"]).sum() / df["DCF"].sum()
556
+ # Return the duration as native float
557
+ return float(duration)
552
558
 
553
559
 
554
560
  def dv01(
@@ -2,7 +2,7 @@ import numpy as np
2
2
  import pandas as pd
3
3
 
4
4
  import pyield.date_converter as dc
5
- import pyield.tpf.tools as bt
5
+ import pyield.tn.tools as bt
6
6
  from pyield import anbima, bday
7
7
  from pyield.date_converter import DateScalar
8
8
 
@@ -266,11 +266,17 @@ def duration(
266
266
  >>> ntnc.duration("21-03-2025", "01-01-2031", 0.067626)
267
267
  4.405363320448003
268
268
  """
269
+ # Return NaN if any input is NaN
270
+ if any(pd.isna(x) for x in [settlement, maturity, rate]):
271
+ return float("NaN")
272
+
273
+ # Validate and normalize dates
269
274
  settlement = dc.convert_input_dates(settlement)
270
275
  maturity = dc.convert_input_dates(maturity)
271
276
 
272
277
  df = cash_flows(settlement, maturity)
273
278
  df["BY"] = bday.count(settlement, df["PaymentDate"]) / 252
274
279
  df["DCF"] = df["CashFlow"] / (1 + rate) ** df["BY"]
275
- np_duration = (df["DCF"] * df["BY"]).sum() / df["DCF"].sum()
276
- return float(np_duration)
280
+ duration = (df["DCF"] * df["BY"]).sum() / df["DCF"].sum()
281
+ # Return the duration as native float
282
+ return float(duration)
@@ -8,7 +8,7 @@ from pyield import date_converter as dc
8
8
  from pyield import interpolator as ip
9
9
  from pyield.b3 import di1
10
10
  from pyield.date_converter import DateScalar
11
- from pyield.tpf import tools
11
+ from pyield.tn import tools
12
12
 
13
13
  """
14
14
  Constants calculated as per Anbima Rules
@@ -645,14 +645,20 @@ def duration(
645
645
  >>> ntnf.duration("02-09-2024", "01-01-2035", 0.121785)
646
646
  6.32854218039796
647
647
  """
648
+ # Return NaN if any input is NaN
649
+ if any(pd.isna(x) for x in [settlement, maturity, rate]):
650
+ return float("NaN")
651
+
652
+ # Validate and normalize input dates
648
653
  settlement = dc.convert_input_dates(settlement)
649
654
  maturity = dc.convert_input_dates(maturity)
650
655
 
651
656
  df = cash_flows(settlement, maturity)
652
657
  df["BY"] = bday.count(settlement, df["PaymentDate"]) / 252
653
658
  df["DCF"] = df["CashFlow"] / (1 + rate) ** df["BY"]
654
- np_duration = (df["DCF"] * df["BY"]).sum() / df["DCF"].sum()
655
- return float(np_duration)
659
+ duration = (df["DCF"] * df["BY"]).sum() / df["DCF"].sum()
660
+ # Return the duration as native float
661
+ return float(duration)
656
662
 
657
663
 
658
664
  def dv01(
@@ -1 +0,0 @@
1
- __version__ = "0.35.2"
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes