PYield 0.46.1__tar.gz → 0.46.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 (72) hide show
  1. {pyield-0.46.1 → pyield-0.46.3}/PKG-INFO +12 -1
  2. {pyield-0.46.1 → pyield-0.46.3}/README.md +11 -0
  3. pyield-0.46.3/pyield/__about__.py +1 -0
  4. {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/data_cache.py +1 -1
  5. pyield-0.46.3/pyield/anbima/tpf.py +293 -0
  6. {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/_validar_pregao.py +4 -12
  7. {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/di1.py +4 -4
  8. {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/ptax_api.py +26 -24
  9. {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/trades_monthly.py +3 -0
  10. {pyield-0.46.1 → pyield-0.46.3}/pyield/fwd.py +1 -1
  11. {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/lft.py +35 -36
  12. {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ltn.py +95 -94
  13. {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ntnb.py +210 -205
  14. {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ntnb1.py +30 -30
  15. {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ntnc.py +72 -67
  16. {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ntnf.py +186 -181
  17. pyield-0.46.3/pyield/tn/pre.py +203 -0
  18. {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/utils.py +38 -13
  19. pyield-0.46.1/pyield/__about__.py +0 -1
  20. pyield-0.46.1/pyield/anbima/tpf.py +0 -356
  21. pyield-0.46.1/pyield/tn/pre.py +0 -198
  22. {pyield-0.46.1 → pyield-0.46.3}/.gitignore +0 -0
  23. {pyield-0.46.1 → pyield-0.46.3}/LICENSE +0 -0
  24. {pyield-0.46.1 → pyield-0.46.3}/pyield/__init__.py +0 -0
  25. {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/__init__.py +0 -0
  26. {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/br_numbers.py +0 -0
  27. {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/cache.py +0 -0
  28. {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/converters.py +0 -0
  29. {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/retry.py +0 -0
  30. {pyield-0.46.1 → pyield-0.46.3}/pyield/_internal/types.py +0 -0
  31. {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/__init__.py +0 -0
  32. {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/difusao.py +0 -0
  33. {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/ettj_intraday.py +0 -0
  34. {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/ettj_last.py +0 -0
  35. {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/ima.py +0 -0
  36. {pyield-0.46.1 → pyield-0.46.3}/pyield/anbima/imaq.py +0 -0
  37. {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/__init__.py +0 -0
  38. {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/_contracts.py +0 -0
  39. {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/di_over.py +0 -0
  40. {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/futures/__init__.py +0 -0
  41. {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/futures/common.py +0 -0
  42. {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/futures/historical.py +0 -0
  43. {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/futures/intraday.py +0 -0
  44. {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/intraday_derivatives.py +0 -0
  45. {pyield-0.46.1 → pyield-0.46.3}/pyield/b3/price_report.py +0 -0
  46. {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/__init__.py +0 -0
  47. {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/auction.py +0 -0
  48. {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/copom.py +0 -0
  49. {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/rates.py +0 -0
  50. {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/repo.py +0 -0
  51. {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/trades_intraday.py +0 -0
  52. {pyield-0.46.1 → pyield-0.46.3}/pyield/bc/vna.py +0 -0
  53. {pyield-0.46.1 → pyield-0.46.3}/pyield/bday/__init__.py +0 -0
  54. {pyield-0.46.1 → pyield-0.46.3}/pyield/bday/core.py +0 -0
  55. {pyield-0.46.1 → pyield-0.46.3}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  56. {pyield-0.46.1 → pyield-0.46.3}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  57. {pyield-0.46.1 → pyield-0.46.3}/pyield/bday/holidays/brholidays.py +0 -0
  58. {pyield-0.46.1 → pyield-0.46.3}/pyield/clock.py +0 -0
  59. {pyield-0.46.1 → pyield-0.46.3}/pyield/interpolator.py +0 -0
  60. {pyield-0.46.1 → pyield-0.46.3}/pyield/ipca/__init__.py +0 -0
  61. {pyield-0.46.1 → pyield-0.46.3}/pyield/ipca/historical.py +0 -0
  62. {pyield-0.46.1 → pyield-0.46.3}/pyield/ipca/projected.py +0 -0
  63. {pyield-0.46.1 → pyield-0.46.3}/pyield/py.typed +0 -0
  64. {pyield-0.46.1 → pyield-0.46.3}/pyield/rmd.py +0 -0
  65. {pyield-0.46.1 → pyield-0.46.3}/pyield/selic/__init__.py +0 -0
  66. {pyield-0.46.1 → pyield-0.46.3}/pyield/selic/cpm.py +0 -0
  67. {pyield-0.46.1 → pyield-0.46.3}/pyield/selic/probabilities.py +0 -0
  68. {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/__init__.py +0 -0
  69. {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/auctions.py +0 -0
  70. {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/benchmark.py +0 -0
  71. {pyield-0.46.1 → pyield-0.46.3}/pyield/tn/ntnbprinc.py +0 -0
  72. {pyield-0.46.1 → pyield-0.46.3}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyield
3
- Version: 0.46.1
3
+ Version: 0.46.3
4
4
  Summary: A Python library for analysis of fixed income instruments in Brazil
5
5
  Project-URL: Homepage, https://github.com/crdcj/PYield
6
6
  Project-URL: Documentation, https://crdcj.github.io/PYield
@@ -247,6 +247,17 @@ ntnb.quotation(None, "15-05-2035", 0.06149) # -> nan
247
247
  bday.count(["01-01-2024", None], "01-02-2024") # -> Series: [22, null]
248
248
  ```
249
249
 
250
+ Consultas sem dados disponíveis (data futura, feriado, fim de semana ou
251
+ fonte indisponível) retornam DataFrame vazio ou `nan`, sem lançar exceção:
252
+
253
+ ```python
254
+ from pyield import bc, futures
255
+
256
+ futures("01-01-2030", "DI1").is_empty() # -> True (data futura)
257
+ bc.tpf_monthly_trades("01-01-2030").is_empty() # -> True (mês futuro)
258
+ bc.ptax("25-12-2025") # -> nan (feriado)
259
+ ```
260
+
250
261
  ## Migração para Polars (v0.40.0+)
251
262
 
252
263
  A partir da versão 0.40.0, todas as saídas tabulares passaram de Pandas para **Polars**. Entradas vetoriais que antes aceitavam `pd.Series` e `np.ndarray` agora aceitam apenas listas, tuplas ou `pl.Series`. Para converter:
@@ -203,6 +203,17 @@ ntnb.quotation(None, "15-05-2035", 0.06149) # -> nan
203
203
  bday.count(["01-01-2024", None], "01-02-2024") # -> Series: [22, null]
204
204
  ```
205
205
 
206
+ Consultas sem dados disponíveis (data futura, feriado, fim de semana ou
207
+ fonte indisponível) retornam DataFrame vazio ou `nan`, sem lançar exceção:
208
+
209
+ ```python
210
+ from pyield import bc, futures
211
+
212
+ futures("01-01-2030", "DI1").is_empty() # -> True (data futura)
213
+ bc.tpf_monthly_trades("01-01-2030").is_empty() # -> True (mês futuro)
214
+ bc.ptax("25-12-2025") # -> nan (feriado)
215
+ ```
216
+
206
217
  ## Migração para Polars (v0.40.0+)
207
218
 
208
219
  A partir da versão 0.40.0, todas as saídas tabulares passaram de Pandas para **Polars**. Entradas vetoriais que antes aceitavam `pd.Series` e `np.ndarray` agora aceitam apenas listas, tuplas ou `pl.Series`. Para converter:
@@ -0,0 +1 @@
1
+ __version__ = "0.46.3"
@@ -16,7 +16,7 @@ registro = logging.getLogger(__name__)
16
16
 
17
17
  # Estrutura interna única — usuário não vê isso
18
18
  class _Dataset(Enum):
19
- TPF = ("anbima_tpf.parquet", "ReferenceDate", "TPF (ANBIMA)")
19
+ TPF = ("anbima_tpf.parquet", "data_referencia", "TPF (ANBIMA)")
20
20
  PR = ("b3_price_report.parquet", "TradeDate", "Price Report (B3)")
21
21
 
22
22
  def __init__(self, nome_arquivo: str, coluna_data: str, descricao: str):
@@ -0,0 +1,293 @@
1
+ """Dados do mercado secundário de Títulos Públicos Federais (TPF) da ANBIMA.
2
+
3
+ Exemplo de URL:
4
+ https://www.anbima.com.br/informacoes/merc-sec/arqs/ms240614.txt
5
+
6
+ Exemplo de dado bruto (CSV separado por @, encoding latin1):
7
+ ANBIMA - Associação Brasileira das Entidades dos Mercados Financeiro e de Capitais
8
+
9
+ Titulo@Data Referencia@Codigo SELIC@Data Base/Emissao@Data Vencimento@Tx. Compra@Tx. Venda@Tx. Indicativas@PU@Desvio padrao@Interv. Ind. Inf. (D0)@Interv. Ind. Sup. (D0)@Interv. Ind. Inf. (D+1)@Interv. Ind. Sup. (D+1)@Criterio
10
+ LTN@20250924@100000@20230707@20251001@14,9483@14,9263@14,9375@997,241543@0,00433039162894@14,7341@15,2612@14,7316@15,2689@Calculado
11
+ LTN@20250924@100000@20200206@20260101@14,7741@14,7485@14,7616@963,001853@0,00729826731971@14,7008@14,9986@14,7021@14,9975@Calculado
12
+ """ # noqa: E501
13
+
14
+ import datetime as dt
15
+ import logging
16
+ import socket
17
+ from typing import Literal
18
+
19
+ import polars as pl
20
+ import requests
21
+
22
+ from pyield import bday
23
+ from pyield._internal.br_numbers import float_br, taxa_br
24
+ from pyield._internal.converters import converter_datas, data_referencia_valida
25
+ from pyield._internal.data_cache import obter_dataset_cacheado
26
+ from pyield._internal.retry import retry_padrao
27
+ from pyield._internal.types import DateLike
28
+
29
+ BOND_TYPES = Literal["LFT", "NTN-B", "NTN-C", "LTN", "NTN-F", "PRE"]
30
+
31
+ ANBIMA_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs"
32
+ ANBIMA_RTM_HOSTNAME = "www.anbima.associados.rtm"
33
+ ANBIMA_RTM_URL = f"http://{ANBIMA_RTM_HOSTNAME}/merc_sec/arqs"
34
+
35
+ # Antes de 13/05/2014 o arquivo era zipado e o endpoint terminava com ".exe"
36
+ DATA_MUDANCA_FORMATO = dt.date(2014, 5, 13)
37
+
38
+ DIAS_RETENCAO_PUBLICA = 5
39
+
40
+ # Colunas selecionadas pela função pública tpf()
41
+ COLUNAS_TPF = (
42
+ "titulo",
43
+ "data_referencia",
44
+ "codigo_selic",
45
+ "data_base",
46
+ "data_vencimento",
47
+ "pu",
48
+ "taxa_compra",
49
+ "taxa_venda",
50
+ "taxa_indicativa",
51
+ )
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+
56
+ def _mapear_tipo_titulo(tipo_titulo: str) -> list[str]:
57
+ tipo_titulo = tipo_titulo.upper()
58
+ mapa_titulos = {
59
+ "PRE": ["LTN", "NTN-F"],
60
+ "NTNB": ["NTN-B"],
61
+ "NTNC": ["NTN-C"],
62
+ "NTNF": ["NTN-F"],
63
+ }
64
+ return mapa_titulos.get(tipo_titulo, [tipo_titulo])
65
+
66
+
67
+ def _montar_nome_arquivo(data: dt.date) -> str:
68
+ data_url = data.strftime("%y%m%d")
69
+ if data < DATA_MUDANCA_FORMATO:
70
+ nome_arquivo = f"ms{data_url}.exe"
71
+ else:
72
+ nome_arquivo = f"ms{data_url}.txt"
73
+ return nome_arquivo
74
+
75
+
76
+ def _montar_url_arquivo(data: dt.date) -> str:
77
+ ultimo_dia_util = bday.last_business_day()
78
+ qtd_dias_uteis = bday.count(data, ultimo_dia_util)
79
+ if qtd_dias_uteis > DIAS_RETENCAO_PUBLICA:
80
+ # Para datas com mais de 5 dias úteis, apenas os dados da RTM estão disponíveis
81
+ logger.info("Tentando buscar dados RTM para %s", data.strftime("%d/%m/%Y"))
82
+ url_arquivo = f"{ANBIMA_RTM_URL}/{_montar_nome_arquivo(data)}"
83
+ else:
84
+ url_arquivo = f"{ANBIMA_URL}/{_montar_nome_arquivo(data)}"
85
+ return url_arquivo
86
+
87
+
88
+ @retry_padrao
89
+ def _obter_csv(data: dt.date) -> bytes:
90
+ url_arquivo = _montar_url_arquivo(data)
91
+ resposta = requests.get(url_arquivo, timeout=10)
92
+ resposta.raise_for_status()
93
+ return resposta.content
94
+
95
+
96
+ def _parsear_df(csv_bytes: bytes) -> pl.DataFrame:
97
+ """Converte bytes brutos do CSV da ANBIMA em DataFrame (tudo string)."""
98
+ return pl.read_csv(
99
+ source=csv_bytes,
100
+ skip_lines=2,
101
+ separator="@",
102
+ null_values=["--"],
103
+ infer_schema=False,
104
+ encoding="latin1",
105
+ )
106
+
107
+
108
+ def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
109
+ """Renomeia, converte tipos e define a ordem das colunas."""
110
+ return df.select(
111
+ titulo=pl.col("Titulo"),
112
+ data_referencia=pl.col("Data Referencia").str.to_date("%Y%m%d"),
113
+ codigo_selic=pl.col("Codigo SELIC").cast(pl.Int64),
114
+ data_base=pl.col("Data Base/Emissao").str.to_date("%Y%m%d"),
115
+ data_vencimento=pl.col("Data Vencimento").str.to_date("%Y%m%d"),
116
+ taxa_compra=taxa_br("Tx. Compra"),
117
+ taxa_venda=taxa_br("Tx. Venda"),
118
+ taxa_indicativa=taxa_br("Tx. Indicativas"),
119
+ pu=float_br("PU"),
120
+ desvio_padrao=float_br("Desvio padrao"),
121
+ taxa_intervalo_inf_d0=taxa_br("Interv. Ind. Inf. (D0)"),
122
+ taxa_intervalo_sup_d0=taxa_br("Interv. Ind. Sup. (D0)"),
123
+ taxa_intervalo_inf_d1=taxa_br("Interv. Ind. Inf. (D+1)"),
124
+ taxa_intervalo_sup_d1=taxa_br("Interv. Ind. Sup. (D+1)"),
125
+ criterio=pl.col("Criterio"),
126
+ )
127
+
128
+
129
+ def _buscar_dados_tpf(data: dt.date) -> pl.DataFrame:
130
+ """Busca e processa dados do mercado secundário de TPF na fonte ANBIMA.
131
+
132
+ Args:
133
+ data: Data de referência.
134
+
135
+ Returns:
136
+ DataFrame processado ou DataFrame vazio se indisponível.
137
+ """
138
+ url_arquivo = _montar_url_arquivo(data)
139
+
140
+ # Fail-fast: se a URL é RTM e o host não resolve, não adianta tentar
141
+ if ANBIMA_RTM_URL in url_arquivo:
142
+ try:
143
+ socket.gethostbyname(ANBIMA_RTM_HOSTNAME)
144
+ except socket.gaierror:
145
+ data_str = data.strftime("%d/%m/%Y")
146
+ logger.warning(
147
+ f"Não foi possível resolver o host da RTM para {data_str}. "
148
+ "Dados históricos exigem acesso à rede RTM."
149
+ )
150
+ return pl.DataFrame()
151
+
152
+ csv_bytes = _obter_csv(data)
153
+ if not csv_bytes.strip():
154
+ return pl.DataFrame()
155
+
156
+ df = _parsear_df(csv_bytes)
157
+ return _processar_df(df)
158
+
159
+
160
+ def tpf(
161
+ date: DateLike,
162
+ bond_type: BOND_TYPES | None = None,
163
+ ) -> pl.DataFrame:
164
+ """Recupera os dados do mercado secundário de TPF da ANBIMA.
165
+
166
+ Esta função busca taxas indicativas e outros dados de títulos públicos
167
+ brasileiros. Primeiro consulta o cache local; se não houver dados,
168
+ busca diretamente na fonte (ANBIMA).
169
+
170
+ Args:
171
+ date (DateLike): A data de referência para os dados (ex: '2024-06-14').
172
+ bond_type (str, optional): Filtra por tipo de título. Aceita os tipos
173
+ individuais ('LTN', 'NTN-F', 'NTN-B', 'NTN-C', 'LFT') ou 'PRE'
174
+ como atalho para prefixados ('LTN' e 'NTN-F').
175
+
176
+ Returns:
177
+ pl.DataFrame: Um DataFrame contendo os dados solicitados.
178
+ Retorna um DataFrame vazio se não houver dados para a data especificada (ex:
179
+ finais de semana, feriados ou datas futuras).
180
+
181
+ Examples:
182
+ >>> from pyield import anbima
183
+ >>> df = anbima.tpf(date="06-02-2026")
184
+
185
+ Output Columns:
186
+ * titulo (String): tipo do título público (ex: 'LTN', 'NTN-B').
187
+ * data_referencia (Date): data de referência dos dados.
188
+ * codigo_selic (Int64): código do título no SELIC.
189
+ * data_base (Date): data base ou de emissão do título.
190
+ * data_vencimento (Date): data de vencimento do título.
191
+ * pu (Float64): preço unitário (PU) para liquidação em D0.
192
+ * taxa_compra (Float64): taxa de compra em D0 (decimal).
193
+ * taxa_venda (Float64): taxa de venda em D0 (decimal).
194
+ * taxa_indicativa (Float64): taxa indicativa em D0 (decimal).
195
+
196
+ Notes:
197
+ A fonte dos dados segue a seguinte hierarquia:
198
+
199
+ 1. **Cache Local:** Fornece acesso rápido a dados históricos
200
+ desde 01/01/2020.
201
+ 2. **Fonte ANBIMA (fallback):** Se a data não estiver no cache,
202
+ busca automaticamente na fonte. Para datas recentes (até 5 dias
203
+ úteis), usa o site público. Para datas mais antigas, requer
204
+ acesso à rede RTM.
205
+
206
+ Para obter o dado completo direto da fonte (todas as colunas),
207
+ use :func:`fetch_tpf`.
208
+ """
209
+ date = converter_datas(date)
210
+
211
+ if not data_referencia_valida(date):
212
+ return pl.DataFrame()
213
+
214
+ # Cache primeiro; se não tiver, busca na fonte
215
+ df = obter_dataset_cacheado("tpf")
216
+ if not df.is_empty():
217
+ df = df.filter(pl.col("data_referencia") == date)
218
+ if df.is_empty():
219
+ df = _buscar_dados_tpf(date)
220
+ if df.is_empty():
221
+ return pl.DataFrame()
222
+
223
+ df = df.select(col for col in COLUNAS_TPF if col in df.columns)
224
+
225
+ if bond_type:
226
+ tipos_titulo = _mapear_tipo_titulo(bond_type)
227
+ df = df.filter(pl.col("titulo").is_in(tipos_titulo))
228
+
229
+ return df.sort("data_referencia", "titulo", "data_vencimento")
230
+
231
+
232
+ def fetch_tpf(
233
+ date: DateLike,
234
+ ) -> pl.DataFrame:
235
+ """Busca os dados do mercado secundário de TPF direto da fonte ANBIMA.
236
+
237
+ Retorna todas as colunas publicadas pela ANBIMA, sem cache e sem
238
+ filtro de colunas. Indicado para uso em jobs e pipelines de dados.
239
+
240
+ Args:
241
+ date (DateLike): Data de referência (ex: '2024-06-14').
242
+
243
+ Returns:
244
+ pl.DataFrame: DataFrame com todas as colunas da ANBIMA.
245
+ Retorna DataFrame vazio se não houver dados.
246
+ """
247
+ date = converter_datas(date)
248
+
249
+ if not data_referencia_valida(date):
250
+ return pl.DataFrame()
251
+
252
+ df = _buscar_dados_tpf(date)
253
+ if df.is_empty():
254
+ return pl.DataFrame()
255
+
256
+ return df.sort("data_referencia", "titulo", "data_vencimento")
257
+
258
+
259
+ def tpf_maturities(
260
+ date: DateLike,
261
+ bond_type: BOND_TYPES,
262
+ ) -> pl.Series:
263
+ """Recupera os vencimentos existentes para um tipo de título na data especificada.
264
+
265
+ Args:
266
+ date (DateLike): A data de referência para os vencimentos.
267
+ bond_type (BOND_TYPES): O tipo de título para filtrar (ex: 'PRE' para 'LTN'
268
+ e 'NTN-F', ou especifique 'LTN' ou 'NTN-F' diretamente).
269
+
270
+ Returns:
271
+ pl.Series: Uma Series contendo as datas de vencimento únicas para o(s)
272
+ tipo(s) de título especificado(s).
273
+
274
+ Examples:
275
+ >>> from pyield import anbima
276
+ >>> anbima.tpf_maturities(date="22-08-2025", bond_type="PRE")
277
+ shape: (18,)
278
+ Series: 'data_vencimento' [date]
279
+ [
280
+ 2025-10-01
281
+ 2026-01-01
282
+ 2026-04-01
283
+ 2026-07-01
284
+ 2026-10-01
285
+ …
286
+ 2030-01-01
287
+ 2031-01-01
288
+ 2032-01-01
289
+ 2033-01-01
290
+ 2035-01-01
291
+ ]
292
+ """
293
+ return tpf(date, bond_type)["data_vencimento"].unique().sort()
@@ -1,10 +1,7 @@
1
1
  import datetime as dt
2
- import logging
3
2
 
4
3
  from pyield import bday, clock
5
4
 
6
- registro = logging.getLogger(__name__)
7
-
8
5
 
9
6
  def data_negociacao_valida(data_negociacao: dt.date) -> bool:
10
7
  """Valida se a data de referência é utilizável para consulta.
@@ -13,24 +10,19 @@ def data_negociacao_valida(data_negociacao: dt.date) -> bool:
13
10
  - Deve ser um dia útil brasileiro.
14
11
  - Não pode estar no futuro (maior que a data corrente no Brasil).
15
12
 
16
- Retorna True se válida, False caso contrário (e loga um aviso).
13
+ Retorna True se válida, False caso contrário.
17
14
  """
18
15
  if data_negociacao > clock.today():
19
- registro.warning(f"A data informada {data_negociacao} está no futuro.")
20
16
  return False
21
17
  if not bday.is_business_day(data_negociacao):
22
- registro.warning(f"A data informada {data_negociacao} não é dia útil.")
23
18
  return False
24
19
 
25
20
  # Não tem pregão na véspera de Natal e Ano Novo
26
- datas_fechadas_especiais = { # Datas especiais
27
- dt.date(data_negociacao.year, 12, 24), # Véspera de Natal
28
- dt.date(data_negociacao.year, 12, 31), # Véspera de Ano Novo
21
+ datas_fechadas_especiais = {
22
+ dt.date(data_negociacao.year, 12, 24),
23
+ dt.date(data_negociacao.year, 12, 31),
29
24
  }
30
25
  if data_negociacao in datas_fechadas_especiais:
31
- registro.warning(
32
- f"Não há pregão na véspera de Natal e de Ano Novo: {data_negociacao}"
33
- )
34
26
  return False
35
27
 
36
28
  return True
@@ -62,11 +62,11 @@ def data(
62
62
  if pre_filter:
63
63
  df_tpf = (
64
64
  obter_dataset_cacheado("tpf")
65
- .filter(pl.col("BondType").is_in(["LTN", "NTN-F"]))
66
- .unique(subset=["MaturityDate", "ReferenceDate"])
65
+ .filter(pl.col("titulo").is_in(["LTN", "NTN-F"]))
66
+ .unique(subset=["data_vencimento", "data_referencia"])
67
67
  .select(
68
- TradeDate_tpf=pl.col("ReferenceDate"),
69
- ExpirationDate=bday.offset_expr("MaturityDate", 0),
68
+ TradeDate_tpf=pl.col("data_referencia"),
69
+ ExpirationDate=bday.offset_expr("data_vencimento", 0),
70
70
  )
71
71
  .sort("TradeDate_tpf", "ExpirationDate")
72
72
  )
@@ -69,13 +69,15 @@ def _processar_df(df: pl.DataFrame) -> pl.DataFrame:
69
69
  )
70
70
  .with_columns(
71
71
  data=pl.col("data_hora").cast(pl.Date),
72
+ hora=pl.col("data_hora").dt.time(),
72
73
  cotacao_media=pl.mean_horizontal("cotacao_compra", "cotacao_venda").round(
73
74
  5
74
75
  ),
75
76
  )
76
- .unique(subset=["data"], keep="last")
77
- .select("data", "data_hora", "cotacao_compra", "cotacao_venda", "cotacao_media")
78
77
  .sort("data_hora")
78
+ .unique(subset=["data"], keep="last")
79
+ .select("data", "hora", "cotacao_compra", "cotacao_venda", "cotacao_media")
80
+ .sort("data")
79
81
  )
80
82
 
81
83
 
@@ -100,7 +102,7 @@ def ptax_series(
100
102
 
101
103
  Output Columns:
102
104
  * data (Date): data da cotação.
103
- * data_hora (Datetime): data e hora da cotação.
105
+ * hora (Time): hora da cotação.
104
106
  * cotacao_compra (Float64): taxa de compra em R$.
105
107
  * cotacao_venda (Float64): taxa de venda em R$.
106
108
  * cotacao_media (Float64): média de compra/venda (5 casas).
@@ -122,30 +124,30 @@ def ptax_series(
122
124
  >>> from pyield import bc
123
125
  >>> bc.ptax_series(start="20-04-2025", end="25-04-2025")
124
126
  shape: (4, 5)
125
- ┌────────────┬─────────────────────────┬────────────────┬───────────────┬───────────────┐
126
- │ data ┆ data_hora ┆ cotacao_compra ┆ cotacao_venda ┆ cotacao_media │
127
- │ --- ┆ --- ┆ --- ┆ --- ┆ --- │
128
- │ date ┆ datetime[μs] ┆ f64 ┆ f64 ┆ f64 │
129
- ╞════════════╪═════════════════════════╪════════════════╪═══════════════╪═══════════════╡
130
- │ 2025-04-22 ┆ 2025-04-22 13:09:35.629 ┆ 5.749 ┆ 5.7496 ┆ 5.7493 │
131
- │ 2025-04-23 ┆ 2025-04-23 13:06:30.443 ┆ 5.6874 ┆ 5.688 ┆ 5.6877 │
132
- │ 2025-04-24 ┆ 2025-04-24 13:04:29.639 ┆ 5.6732 ┆ 5.6738 ┆ 5.6735 │
133
- │ 2025-04-25 ┆ 2025-04-25 13:09:26.592 ┆ 5.684 ┆ 5.6846 ┆ 5.6843 │
134
- └────────────┴─────────────────────────┴────────────────┴───────────────┴───────────────┘
127
+ ┌────────────┬──────────────┬────────────────┬───────────────┬───────────────┐
128
+ │ data ┆ hora ┆ cotacao_compra ┆ cotacao_venda ┆ cotacao_media │
129
+ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │
130
+ │ date ┆ time ┆ f64 ┆ f64 ┆ f64 │
131
+ ╞════════════╪══════════════╪════════════════╪═══════════════╪═══════════════╡
132
+ │ 2025-04-22 ┆ 13:09:35.629 ┆ 5.749 ┆ 5.7496 ┆ 5.7493 │
133
+ │ 2025-04-23 ┆ 13:06:30.443 ┆ 5.6874 ┆ 5.688 ┆ 5.6877 │
134
+ │ 2025-04-24 ┆ 13:04:29.639 ┆ 5.6732 ┆ 5.6738 ┆ 5.6735 │
135
+ │ 2025-04-25 ┆ 13:09:26.592 ┆ 5.684 ┆ 5.6846 ┆ 5.6843 │
136
+ └────────────┴──────────────┴────────────────┴───────────────┴───────────────┘
135
137
 
136
138
  >>> bc.ptax_series(start="02-01-1995", end="06-01-1995")
137
139
  shape: (5, 5)
138
- ┌────────────┬─────────────────────┬────────────────┬───────────────┬───────────────┐
139
- │ data ┆ data_hora ┆ cotacao_compra ┆ cotacao_venda ┆ cotacao_media │
140
- │ --- ┆ --- ┆ --- ┆ --- ┆ --- │
141
- │ date ┆ datetime[μs] ┆ f64 ┆ f64 ┆ f64 │
142
- ╞════════════╪═════════════════════╪════════════════╪═══════════════╪═══════════════╡
143
- │ 1995-01-02 ┆ 1995-01-02 18:20:00 ┆ 0.843 ┆ 0.845 ┆ 0.844 │
144
- │ 1995-01-03 ┆ 1995-01-03 18:25:00 ┆ 0.844 ┆ 0.846 ┆ 0.845 │
145
- │ 1995-01-04 ┆ 1995-01-04 18:12:00 ┆ 0.844 ┆ 0.846 ┆ 0.845 │
146
- │ 1995-01-05 ┆ 1995-01-05 18:07:00 ┆ 0.842 ┆ 0.844 ┆ 0.843 │
147
- │ 1995-01-06 ┆ 1995-01-06 18:12:00 ┆ 0.839 ┆ 0.841 ┆ 0.84 │
148
- └────────────┴─────────────────────┴────────────────┴───────────────┴───────────────┘
140
+ ┌────────────┬──────────┬────────────────┬───────────────┬───────────────┐
141
+ │ data ┆ hora ┆ cotacao_compra ┆ cotacao_venda ┆ cotacao_media │
142
+ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │
143
+ │ date ┆ time ┆ f64 ┆ f64 ┆ f64 │
144
+ ╞════════════╪══════════╪════════════════╪═══════════════╪═══════════════╡
145
+ │ 1995-01-02 ┆ 18:20:00 ┆ 0.843 ┆ 0.845 ┆ 0.844 │
146
+ │ 1995-01-03 ┆ 18:25:00 ┆ 0.844 ┆ 0.846 ┆ 0.845 │
147
+ │ 1995-01-04 ┆ 18:12:00 ┆ 0.844 ┆ 0.846 ┆ 0.845 │
148
+ │ 1995-01-05 ┆ 18:07:00 ┆ 0.842 ┆ 0.844 ┆ 0.843 │
149
+ │ 1995-01-06 ┆ 18:12:00 ┆ 0.839 ┆ 0.841 ┆ 0.84 │
150
+ └────────────┴──────────┴────────────────┴───────────────┴───────────────┘
149
151
  """
150
152
  if start:
151
153
  start = cv.converter_datas(start)
@@ -23,6 +23,7 @@ from pyield._internal.cache import ttl_cache
23
23
  from pyield._internal.converters import converter_datas
24
24
  from pyield._internal.retry import retry_padrao
25
25
  from pyield._internal.types import DateLike, any_is_empty
26
+ from pyield.clock import today
26
27
 
27
28
  URL_BASE = "https://www4.bcb.gov.br/pom/demab/negociacoes/download"
28
29
 
@@ -148,6 +149,8 @@ def tpf_monthly_trades(target_date: DateLike, extragroup: bool = False) -> pl.Da
148
149
  if any_is_empty(target_date):
149
150
  return pl.DataFrame()
150
151
  data_alvo = converter_datas(target_date)
152
+ if (data_alvo.year, data_alvo.month) > (today().year, today().month):
153
+ return pl.DataFrame()
151
154
  url = _montar_url(data_alvo, extragroup)
152
155
  conteudo_zip = _baixar_zip(url)
153
156
  arquivo_extraido = _descompactar_zip(conteudo_zip)
@@ -103,7 +103,7 @@ def forwards(
103
103
  >>> # Exemplo com taxas indicativas de NTN-B em 16-09-2025
104
104
  >>> from pyield import ntnb
105
105
  >>> df = ntnb.data("16-09-2025")
106
- >>> yd.forwards(df["BDToMat"], df["IndicativeRate"])
106
+ >>> yd.forwards(df["dias_uteis"], df["taxa_indicativa"])
107
107
  shape: (13,)
108
108
  Series: 'fwd' [f64]
109
109
  [
@@ -1,7 +1,7 @@
1
1
  import polars as pl
2
2
 
3
3
  import pyield._internal.converters as cv
4
- from pyield import anbima, bday
4
+ from pyield import bday
5
5
  from pyield._internal.types import DateLike, any_is_empty
6
6
  from pyield.tn import utils
7
7
 
@@ -17,62 +17,61 @@ def data(date: DateLike) -> pl.DataFrame:
17
17
  pl.DataFrame: DataFrame Polars com os dados de LFT.
18
18
 
19
19
  Output Columns:
20
- - ReferenceDate (Date): Data de referência dos dados.
21
- - BondType (String): Tipo do título (ex.: "LFT").
22
- - SelicCode (Int64): Código do título no SELIC.
23
- - IssueBaseDate (Date): Data base/emissão do título.
24
- - MaturityDate (Date): Data de vencimento do título.
25
- - BDToMat (Int64): Dias úteis entre referência e vencimento.
26
- - AvgMaturity (Float64): Prazo médio do título em anos.
27
- - Price (Float64): Preço unitário (PU).
28
- - BidRate (Float64): Taxa de compra (decimal).
29
- - AskRate (Float64): Taxa de venda (decimal).
30
- - IndicativeRate (Float64): Taxa indicativa (decimal).
31
- - DIRate (Float64): Taxa DI interpolada (flat forward).
32
- - Premium (Float64): Rentabilidade da LFT sobre o DI.
20
+ - data_referencia (Date): Data de referência dos dados.
21
+ - titulo (String): Tipo do título (ex.: "LFT").
22
+ - codigo_selic (Int64): Código do título no SELIC.
23
+ - data_base (Date): Data base de emissão do título.
24
+ - data_vencimento (Date): Data de vencimento do título.
25
+ - dias_uteis (Int64): Dias úteis entre referência e vencimento.
26
+ - prazo_medio (Float64): Prazo médio do título em anos.
27
+ - pu (Float64): Preço unitário (PU).
28
+ - taxa_compra (Float64): Taxa de compra (decimal).
29
+ - taxa_venda (Float64): Taxa de venda (decimal).
30
+ - taxa_indicativa (Float64): Taxa indicativa (decimal).
31
+ - taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
32
+ método flat forward.
33
+ - rentabilidade (Float64): Rentabilidade da LFT sobre o DI.
33
34
 
34
35
  Examples:
35
36
  >>> from pyield import lft
36
37
  >>> df_lft = lft.data("23-08-2024") # doctest: +SKIP
37
38
  """
38
- df = anbima.tpf(date, "LFT")
39
+ df = utils.obter_tpf(date, "LFT")
39
40
  if df.is_empty():
40
41
  return df
41
42
 
42
43
  data_ref = cv.converter_datas(date)
43
44
 
44
- # Adiciona BDToMat (dado derivado, não vem da ANBIMA)
45
45
  df = df.with_columns(
46
- BDToMat=bday.count_expr("ReferenceDate", "MaturityDate"),
46
+ dias_uteis=bday.count_expr("data_referencia", "data_vencimento"),
47
47
  )
48
48
 
49
- # Adiciona AvgMaturity e DIRate
50
49
  df = df.with_columns(
51
- AvgMaturity=pl.col("BDToMat") / 252,
50
+ prazo_medio=pl.col("dias_uteis") / 252,
52
51
  )
53
52
  df = utils.adicionar_taxa_di(df, data_ref)
54
53
 
55
54
  df = df.with_columns(
56
- Premium=pl.struct("IndicativeRate", "DIRate").map_elements(
57
- lambda s: premium(s["IndicativeRate"], s["DIRate"]),
55
+ rentabilidade=pl.struct("taxa_indicativa", "taxa_di").map_elements(
56
+ lambda s: premium(s["taxa_indicativa"], s["taxa_di"]),
58
57
  return_dtype=pl.Float64,
59
58
  )
60
59
  )
61
60
 
62
61
  return df.select(
63
- "ReferenceDate",
64
- "BondType",
65
- "SelicCode",
66
- "IssueBaseDate",
67
- "MaturityDate",
68
- "BDToMat",
69
- "AvgMaturity",
70
- "Price",
71
- "BidRate",
72
- "AskRate",
73
- "IndicativeRate",
74
- "DIRate",
75
- "Premium",
62
+ "data_referencia",
63
+ "titulo",
64
+ "codigo_selic",
65
+ "data_base",
66
+ "data_vencimento",
67
+ "dias_uteis",
68
+ "prazo_medio",
69
+ "pu",
70
+ "taxa_compra",
71
+ "taxa_venda",
72
+ "taxa_indicativa",
73
+ "taxa_di",
74
+ "rentabilidade",
76
75
  )
77
76
 
78
77
 
@@ -90,7 +89,7 @@ def maturities(date: DateLike) -> pl.Series:
90
89
  >>> from pyield import lft
91
90
  >>> lft.maturities("22-08-2024")
92
91
  shape: (14,)
93
- Series: 'MaturityDate' [date]
92
+ Series: 'data_vencimento' [date]
94
93
  [
95
94
  2024-09-01
96
95
  2025-03-01
@@ -105,7 +104,7 @@ def maturities(date: DateLike) -> pl.Series:
105
104
  2030-09-01
106
105
  ]
107
106
  """
108
- return data(date)["MaturityDate"]
107
+ return data(date)["data_vencimento"]
109
108
 
110
109
 
111
110
  def quotation(