PYield 0.35.2__tar.gz → 0.35.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.
- {pyield-0.35.2 → pyield-0.35.3}/PKG-INFO +1 -1
- pyield-0.35.3/pyield/__about__.py +1 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/__init__.py +6 -5
- {pyield-0.35.2 → pyield-0.35.3}/pyield/bc/auction.py +72 -63
- pyield-0.35.3/pyield/tn/__init__.py +3 -0
- pyield-0.35.3/pyield/tn/auction.py +91 -0
- {pyield-0.35.2/pyield/tpf → pyield-0.35.3/pyield/tn}/lft.py +1 -1
- {pyield-0.35.2/pyield/tpf → pyield-0.35.3/pyield/tn}/ltn.py +1 -1
- {pyield-0.35.2/pyield/tpf → pyield-0.35.3/pyield/tn}/ntnb.py +9 -3
- {pyield-0.35.2/pyield/tpf → pyield-0.35.3/pyield/tn}/ntnc.py +9 -3
- {pyield-0.35.2/pyield/tpf → pyield-0.35.3/pyield/tn}/ntnf.py +9 -3
- pyield-0.35.2/pyield/__about__.py +0 -1
- pyield-0.35.2/pyield/tpf/__init__.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/.gitignore +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/LICENSE +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/README.md +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/anbima/__init__.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/anbima/ettj.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/anbima/ima.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/anbima/imaq.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/anbima/ipca.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/anbima/tpf.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/b3/__init__.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/b3/di1.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/b3/di_over.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/b3/futures/__init__.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/b3/futures/common.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/b3/futures/historical.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/b3/futures/intraday.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/b3/futures/xml.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/bc/__init__.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/bc/bcdata.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/bc/ptax.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/bc/repo.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/bc/trades_intraday.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/bc/trades_monthly.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/bc/vna.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/bday.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/conftest.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/data_cache.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/date_converter.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/fwd.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/holidays/__init__.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/holidays/brholidays.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/ibge/__init__.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/ibge/ipca.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/interpolator.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/py.typed +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyield/retry.py +0 -0
- {pyield-0.35.2/pyield/tpf → pyield-0.35.3/pyield/tn}/tools.py +0 -0
- {pyield-0.35.2 → pyield-0.35.3}/pyproject.toml +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.35.3"
|
|
@@ -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.
|
|
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
|
|
@@ -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.
|
|
20
|
-
from pyield.
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
86
|
-
df["Value"] = (1_000_000 * df["Value"]).round(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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"] >=
|
|
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á
|
|
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
|
-
|
|
134
|
-
df[
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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 =
|
|
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(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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([
|
|
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
|
-
|
|
180
|
-
|
|
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
|
-
|
|
183
|
-
df["
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
"
|
|
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["
|
|
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["
|
|
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 = {"
|
|
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}'"
|
|
@@ -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
|
|
|
4
4
|
import pyield.date_converter as dc
|
|
5
5
|
import pyield.interpolator as ip
|
|
6
|
-
import pyield.
|
|
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
|
-
|
|
551
|
-
|
|
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.
|
|
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
|
-
|
|
276
|
-
|
|
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.
|
|
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
|
-
|
|
655
|
-
|
|
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
|
|
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
|