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.
- {pyield-0.26.0 → pyield-0.26.2}/PKG-INFO +1 -1
- pyield-0.26.2/pyield/__about__.py +1 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/__init__.py +10 -9
- pyield-0.26.2/pyield/bc/auction.py +308 -0
- pyield-0.26.2/pyield/ima.py +143 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/ltn.py +2 -1
- {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/ntnf.py +2 -1
- {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/tools.py +1 -1
- pyield-0.26.0/pyield/__about__.py +0 -1
- {pyield-0.26.0 → pyield-0.26.2}/.gitignore +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/LICENSE +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/README.md +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/anbima.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/b3_futures/__init__.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/b3_futures/common.py +0 -0
- {pyield-0.26.0/pyield → pyield-0.26.2/pyield/b3_futures}/di.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/b3_futures/historical.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/b3_futures/intraday.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/b3_futures/xml.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/bday.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/conftest.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/data_cache.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/date_converter.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/holidays/__init__.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/holidays/brholidays.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/indicators.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/interpolator.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/projections.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/py.typed +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/tools.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/__init__.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/lft.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyield/tpf/ntnb.py +0 -0
- {pyield-0.26.0 → pyield-0.26.2}/pyproject.toml +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.26.2"
|
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
from pyield import anbima, bday
|
|
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()
|
|
@@ -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
|
|
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
|
|
|
@@ -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
|
|
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
|