PYield 0.46.2__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 (71) hide show
  1. {pyield-0.46.2 → pyield-0.46.3}/PKG-INFO +1 -1
  2. pyield-0.46.3/pyield/__about__.py +1 -0
  3. {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/data_cache.py +1 -1
  4. pyield-0.46.3/pyield/anbima/tpf.py +293 -0
  5. {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/di1.py +4 -4
  6. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/lft.py +3 -2
  7. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ltn.py +3 -2
  8. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ntnb.py +3 -2
  9. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ntnc.py +3 -2
  10. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ntnf.py +8 -10
  11. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/utils.py +1 -15
  12. pyield-0.46.2/pyield/__about__.py +0 -1
  13. pyield-0.46.2/pyield/anbima/tpf.py +0 -356
  14. {pyield-0.46.2 → pyield-0.46.3}/.gitignore +0 -0
  15. {pyield-0.46.2 → pyield-0.46.3}/LICENSE +0 -0
  16. {pyield-0.46.2 → pyield-0.46.3}/README.md +0 -0
  17. {pyield-0.46.2 → pyield-0.46.3}/pyield/__init__.py +0 -0
  18. {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/__init__.py +0 -0
  19. {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/br_numbers.py +0 -0
  20. {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/cache.py +0 -0
  21. {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/converters.py +0 -0
  22. {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/retry.py +0 -0
  23. {pyield-0.46.2 → pyield-0.46.3}/pyield/_internal/types.py +0 -0
  24. {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/__init__.py +0 -0
  25. {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/difusao.py +0 -0
  26. {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/ettj_intraday.py +0 -0
  27. {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/ettj_last.py +0 -0
  28. {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/ima.py +0 -0
  29. {pyield-0.46.2 → pyield-0.46.3}/pyield/anbima/imaq.py +0 -0
  30. {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/__init__.py +0 -0
  31. {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/_contracts.py +0 -0
  32. {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/_validar_pregao.py +0 -0
  33. {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/di_over.py +0 -0
  34. {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/futures/__init__.py +0 -0
  35. {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/futures/common.py +0 -0
  36. {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/futures/historical.py +0 -0
  37. {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/futures/intraday.py +0 -0
  38. {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/intraday_derivatives.py +0 -0
  39. {pyield-0.46.2 → pyield-0.46.3}/pyield/b3/price_report.py +0 -0
  40. {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/__init__.py +0 -0
  41. {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/auction.py +0 -0
  42. {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/copom.py +0 -0
  43. {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/ptax_api.py +0 -0
  44. {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/rates.py +0 -0
  45. {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/repo.py +0 -0
  46. {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/trades_intraday.py +0 -0
  47. {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/trades_monthly.py +0 -0
  48. {pyield-0.46.2 → pyield-0.46.3}/pyield/bc/vna.py +0 -0
  49. {pyield-0.46.2 → pyield-0.46.3}/pyield/bday/__init__.py +0 -0
  50. {pyield-0.46.2 → pyield-0.46.3}/pyield/bday/core.py +0 -0
  51. {pyield-0.46.2 → pyield-0.46.3}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  52. {pyield-0.46.2 → pyield-0.46.3}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  53. {pyield-0.46.2 → pyield-0.46.3}/pyield/bday/holidays/brholidays.py +0 -0
  54. {pyield-0.46.2 → pyield-0.46.3}/pyield/clock.py +0 -0
  55. {pyield-0.46.2 → pyield-0.46.3}/pyield/fwd.py +0 -0
  56. {pyield-0.46.2 → pyield-0.46.3}/pyield/interpolator.py +0 -0
  57. {pyield-0.46.2 → pyield-0.46.3}/pyield/ipca/__init__.py +0 -0
  58. {pyield-0.46.2 → pyield-0.46.3}/pyield/ipca/historical.py +0 -0
  59. {pyield-0.46.2 → pyield-0.46.3}/pyield/ipca/projected.py +0 -0
  60. {pyield-0.46.2 → pyield-0.46.3}/pyield/py.typed +0 -0
  61. {pyield-0.46.2 → pyield-0.46.3}/pyield/rmd.py +0 -0
  62. {pyield-0.46.2 → pyield-0.46.3}/pyield/selic/__init__.py +0 -0
  63. {pyield-0.46.2 → pyield-0.46.3}/pyield/selic/cpm.py +0 -0
  64. {pyield-0.46.2 → pyield-0.46.3}/pyield/selic/probabilities.py +0 -0
  65. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/__init__.py +0 -0
  66. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/auctions.py +0 -0
  67. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/benchmark.py +0 -0
  68. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ntnb1.py +0 -0
  69. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/ntnbprinc.py +0 -0
  70. {pyield-0.46.2 → pyield-0.46.3}/pyield/tn/pre.py +0 -0
  71. {pyield-0.46.2 → 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.2
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
@@ -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()
@@ -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
  )
@@ -20,7 +20,7 @@ def data(date: DateLike) -> pl.DataFrame:
20
20
  - data_referencia (Date): Data de referência dos dados.
21
21
  - titulo (String): Tipo do título (ex.: "LFT").
22
22
  - codigo_selic (Int64): Código do título no SELIC.
23
- - data_base (Date): Data base/emissão do título.
23
+ - data_base (Date): Data base de emissão do título.
24
24
  - data_vencimento (Date): Data de vencimento do título.
25
25
  - dias_uteis (Int64): Dias úteis entre referência e vencimento.
26
26
  - prazo_medio (Float64): Prazo médio do título em anos.
@@ -28,7 +28,8 @@ def data(date: DateLike) -> pl.DataFrame:
28
28
  - taxa_compra (Float64): Taxa de compra (decimal).
29
29
  - taxa_venda (Float64): Taxa de venda (decimal).
30
30
  - taxa_indicativa (Float64): Taxa indicativa (decimal).
31
- - taxa_di (Float64): Taxa DI interpolada pelo método flat forward.
31
+ - taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
32
+ método flat forward.
32
33
  - rentabilidade (Float64): Rentabilidade da LFT sobre o DI.
33
34
 
34
35
  Examples:
@@ -23,7 +23,7 @@ def data(date: DateLike) -> pl.DataFrame:
23
23
  - data_referencia (Date): Data de referência dos dados.
24
24
  - titulo (String): Tipo do título (ex.: "LTN").
25
25
  - codigo_selic (Int64): Código do título no SELIC.
26
- - data_base (Date): Data base/emissão do título.
26
+ - data_base (Date): Data base de emissão do título.
27
27
  - data_vencimento (Date): Data de vencimento do título.
28
28
  - dias_uteis (Int64): Dias úteis entre referência e vencimento.
29
29
  - duration (Float64): Macaulay Duration do título (anos).
@@ -34,7 +34,8 @@ def data(date: DateLike) -> pl.DataFrame:
34
34
  - taxa_compra (Float64): Taxa de compra (decimal).
35
35
  - taxa_venda (Float64): Taxa de venda (decimal).
36
36
  - taxa_indicativa (Float64): Taxa indicativa (decimal).
37
- - taxa_di (Float64): Taxa DI interpolada pelo método flat forward.
37
+ - taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
38
+ método flat forward.
38
39
  - spread_di (Float64): Spread sobre o DI (também conhecido como
39
40
  prêmio).
40
41
  - rentabilidade (Float64): Rentabilidade diária da LTN sobre o DI.
@@ -37,7 +37,7 @@ def data(date: DateLike) -> pl.DataFrame:
37
37
  - data_referencia (Date): Data de referência dos dados.
38
38
  - titulo (String): Tipo do título (ex.: "NTN-B").
39
39
  - codigo_selic (Int64): Código do título no SELIC.
40
- - data_base (Date): Data base/emissão do título.
40
+ - data_base (Date): Data base de emissão do título.
41
41
  - data_vencimento (Date): Data de vencimento do título.
42
42
  - dias_uteis (Int64): Dias úteis entre referência e vencimento.
43
43
  - duration (Float64): Macaulay Duration do título (anos).
@@ -48,7 +48,8 @@ def data(date: DateLike) -> pl.DataFrame:
48
48
  - taxa_compra (Float64): Taxa de compra (decimal).
49
49
  - taxa_venda (Float64): Taxa de venda (decimal).
50
50
  - taxa_indicativa (Float64): Taxa indicativa (decimal).
51
- - taxa_di (Float64): Taxa DI interpolada pelo método flat forward.
51
+ - taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
52
+ método flat forward.
52
53
  - taxa_zero (Float64): Taxa zero real (via bootstrap das taxas indicativas).
53
54
  - taxa_forward (Float64): Taxa forward real (a partir das taxas zero).
54
55
  - inflacao_implicita (Float64): Inflação implícita (breakeven) calculada
@@ -53,7 +53,7 @@ def data(date: DateLike) -> pl.DataFrame:
53
53
  - data_referencia (Date): Data de referência dos dados.
54
54
  - titulo (String): Tipo do título (ex.: "NTN-C").
55
55
  - codigo_selic (Int64): Código do título no SELIC.
56
- - data_base (Date): Data base/emissão do título.
56
+ - data_base (Date): Data base de emissão do título.
57
57
  - data_vencimento (Date): Data de vencimento do título.
58
58
  - dias_uteis (Int64): Dias úteis entre referência e vencimento.
59
59
  - duration (Float64): Macaulay Duration do título (anos).
@@ -64,7 +64,8 @@ def data(date: DateLike) -> pl.DataFrame:
64
64
  - taxa_compra (Float64): Taxa de compra (decimal).
65
65
  - taxa_venda (Float64): Taxa de venda (decimal).
66
66
  - taxa_indicativa (Float64): Taxa indicativa (decimal).
67
- - taxa_di (Float64): Taxa DI interpolada pelo método flat forward.
67
+ - taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
68
+ método flat forward.
68
69
 
69
70
  Examples:
70
71
  >>> from pyield import ntnc
@@ -43,7 +43,7 @@ def data(date: DateLike) -> pl.DataFrame:
43
43
  - data_referencia (Date): Data de referência dos dados.
44
44
  - titulo (String): Tipo do título (ex.: "NTN-F").
45
45
  - codigo_selic (Int64): Código do título no SELIC.
46
- - data_base (Date): Data base/emissão do título.
46
+ - data_base (Date): Data base de emissão do título.
47
47
  - data_vencimento (Date): Data de vencimento do título.
48
48
  - dias_uteis (Int64): Dias úteis entre referência e vencimento.
49
49
  - prazo_medio (Float64): Prazo médio do título em dias corridos.
@@ -54,11 +54,11 @@ def data(date: DateLike) -> pl.DataFrame:
54
54
  - taxa_compra (Float64): Taxa de compra (decimal).
55
55
  - taxa_venda (Float64): Taxa de venda (decimal).
56
56
  - taxa_indicativa (Float64): Taxa indicativa (decimal).
57
- - taxa_di (Float64): Taxa DI interpolada pelo método flat forward.
57
+ - taxa_di (Float64): Taxa de ajuste do DI Futuro interpolada pelo
58
+ método flat forward.
58
59
  - taxa_zero (Float64): Taxa zero (zero cupom via bootstrap).
59
- - spread_di (Float64): Spread sobre o DI (também conhecido como
60
- prêmio).
61
- - spread_di_liquido (Float64): Spread líquido sobre a curva DI.
60
+ - spread_di (Float64): Spread sobre o DI (conhecido como prêmio).
61
+ - spread_di_limpo (Float64): Spread limpo sobre a curva DI (conhecido como prêmio limpo).
62
62
  - rentabilidade (Float64): Rentabilidade da NTN-F sobre a curva DI.
63
63
 
64
64
  Examples:
@@ -98,7 +98,7 @@ def data(date: DateLike) -> pl.DataFrame:
98
98
  # Calcula spreads e rentabilidade para cada vencimento
99
99
  df = df.with_columns(
100
100
  spread_di=pl.col("taxa_indicativa") - pl.col("taxa_di"),
101
- spread_di_liquido=pl.struct("data_vencimento", "taxa_indicativa").map_elements(
101
+ spread_di_limpo=pl.struct("data_vencimento", "taxa_indicativa").map_elements(
102
102
  lambda row: di_net_spread(
103
103
  settlement=date, # Usa a variável externa explicitamente aqui
104
104
  ntnf_maturity=row["data_vencimento"],
@@ -138,7 +138,7 @@ def data(date: DateLike) -> pl.DataFrame:
138
138
  "taxa_di",
139
139
  "taxa_zero",
140
140
  "spread_di",
141
- "spread_di_liquido",
141
+ "spread_di_limpo",
142
142
  "rentabilidade",
143
143
  )
144
144
 
@@ -293,9 +293,7 @@ def cash_flows(
293
293
  )
294
294
 
295
295
  if adj_payment_dates:
296
- df = df.with_columns(
297
- data_pagamento=bday.offset_expr("data_pagamento", 0)
298
- )
296
+ df = df.with_columns(data_pagamento=bday.offset_expr("data_pagamento", 0))
299
297
  return df
300
298
 
301
299
 
@@ -11,18 +11,6 @@ from pyield.anbima.tpf import BOND_TYPES
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
- MAPA_COLUNAS_TPF = {
15
- "ReferenceDate": "data_referencia",
16
- "BondType": "titulo",
17
- "SelicCode": "codigo_selic",
18
- "IssueBaseDate": "data_base",
19
- "MaturityDate": "data_vencimento",
20
- "Price": "pu",
21
- "BidRate": "taxa_compra",
22
- "AskRate": "taxa_venda",
23
- "IndicativeRate": "taxa_indicativa",
24
- }
25
-
26
14
  COLUNAS_DADOS_TPF = (
27
15
  "data_referencia",
28
16
  "titulo",
@@ -51,9 +39,7 @@ def obter_tpf(
51
39
  """Busca dados de ``anbima.tpf()`` no padrão de colunas usado por ``tn``."""
52
40
  from pyield import anbima # noqa: PLC0415
53
41
 
54
- return anbima.tpf(date, tipo_titulo).select(tuple(MAPA_COLUNAS_TPF)).rename(
55
- MAPA_COLUNAS_TPF
56
- )
42
+ return anbima.tpf(date, tipo_titulo).select(COLUNAS_DADOS_TPF)
57
43
 
58
44
 
59
45
  def adicionar_taxa_di(df: pl.DataFrame, data_ref: dt.date) -> pl.DataFrame:
@@ -1 +0,0 @@
1
- __version__ = "0.46.2"
@@ -1,356 +0,0 @@
1
- import datetime as dt
2
- import logging
3
- import socket
4
- from typing import Literal
5
-
6
- import polars as pl
7
- import polars.selectors as cs
8
- import requests
9
- from requests.exceptions import HTTPError, RequestException
10
-
11
- from pyield import bday
12
- from pyield._internal.converters import converter_datas, data_referencia_valida
13
- from pyield._internal.data_cache import obter_dataset_cacheado
14
- from pyield._internal.retry import retry_padrao
15
- from pyield._internal.types import DateLike
16
-
17
- BOND_TYPES = Literal["LFT", "NTN-B", "NTN-C", "LTN", "NTN-F", "PRE"]
18
-
19
- ANBIMA_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs"
20
- ANBIMA_RTM_HOSTNAME = "www.anbima.associados.rtm"
21
- ANBIMA_RTM_URL = f"http://{ANBIMA_RTM_HOSTNAME}/merc_sec/arqs"
22
- # Exemplo de URL: https://www.anbima.com.br/informacoes/merc-sec/arqs/ms240614.txt
23
-
24
- # Antes de 13/05/2014 o arquivo era zipado e o endpoint terminava com ".exe"
25
- DATA_MUDANCA_FORMATO = dt.date(2014, 5, 13)
26
-
27
- DIAS_RETENCAO_PUBLICA = 5
28
-
29
- # Única fonte de verdade para colunas do CSV: (nome_csv, nome_novo, tipo)
30
- # Colunas de data são lidas como String e convertidas em _processar_df_bruto
31
- TPF_COLUNAS = [
32
- ("Titulo", "BondType", pl.String),
33
- ("Data Referencia", "ReferenceDate", pl.String),
34
- ("Codigo SELIC", "SelicCode", pl.Int64),
35
- ("Data Base/Emissao", "IssueBaseDate", pl.String),
36
- ("Data Vencimento", "MaturityDate", pl.String),
37
- ("Tx. Compra", "BidRate", pl.Float64),
38
- ("Tx. Venda", "AskRate", pl.Float64),
39
- ("Tx. Indicativas", "IndicativeRate", pl.Float64),
40
- ("PU", "Price", pl.Float64),
41
- ("Desvio padrao", "StdDev", pl.Float64),
42
- ("Interv. Ind. Inf. (D0)", "LowerBoundRateD0", pl.Float64),
43
- ("Interv. Ind. Sup. (D0)", "UpperBoundRateD0", pl.Float64),
44
- ("Interv. Ind. Inf. (D+1)", "LowerBoundRateD1", pl.Float64),
45
- ("Interv. Ind. Sup. (D+1)", "UpperBoundRateD1", pl.Float64),
46
- ("Criterio", "Criteria", pl.String),
47
- ]
48
-
49
- # Derivados automaticamente
50
- ESQUEMA_TPF = {csv: tipo for csv, _, tipo in TPF_COLUNAS}
51
- MAPA_NOMES_COLUNAS = {csv: novo for csv, novo, _ in TPF_COLUNAS}
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 _ler_csv(csv_texto: bytes) -> pl.DataFrame:
97
- """
98
- Exemplo de arquivo bruto da ANBIMA:
99
- ANBIMA - Associação Brasileira das Entidades dos Mercados Financeiro e de Capitais
100
-
101
- 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
102
- LTN@20250924@100000@20230707@20251001@14,9483@14,9263@14,9375@997,241543@0,00433039162894@14,7341@15,2612@14,7316@15,2689@Calculado
103
- LTN@20250924@100000@20200206@20260101@14,7741@14,7485@14,7616@963,001853@0,00729826731971@14,7008@14,9986@14,7021@14,9975@Calculado
104
- LTN@20250924@100000@20240105@20260401@14,7357@14,707@14,7205@931,607124@0,00317937979329@14,5525@14,9847@14,5669@14,9959@Calculado
105
- ...
106
- """ # noqa
107
- df = pl.read_csv(
108
- source=csv_texto,
109
- skip_lines=2,
110
- separator="@",
111
- null_values=["--"],
112
- decimal_comma=True,
113
- schema_overrides=ESQUEMA_TPF,
114
- encoding="latin1",
115
- )
116
- return df
117
-
118
-
119
- def _processar_df_bruto(df: pl.DataFrame) -> pl.DataFrame:
120
- df = (
121
- df.rename(MAPA_NOMES_COLUNAS)
122
- .with_columns(
123
- # Remove o percentual das taxas
124
- # Colunas de taxa têm valores percentuais com 4 casas decimais
125
- # Arredonda para 6 casas decimais para minimizar erros de ponto flutuante
126
- cs.contains("Rate").truediv(100).round(6),
127
- cs.ends_with("Date").str.to_date(format="%Y%m%d"),
128
- )
129
- # Substituir eventuais NaNs por None para compatibilidade com bancos de dados
130
- .with_columns(cs.float().fill_nan(None))
131
- )
132
- return df
133
-
134
-
135
- def _selecionar_e_ordenar_colunas(df: pl.DataFrame) -> pl.DataFrame:
136
- """Reordena as colunas do DataFrame de acordo com a ordem especificada."""
137
- ordem_colunas = [
138
- "BondType",
139
- "ReferenceDate",
140
- "SelicCode",
141
- "IssueBaseDate",
142
- "MaturityDate",
143
- "Price",
144
- "BidRate",
145
- "AskRate",
146
- "IndicativeRate",
147
- ]
148
- ordem_colunas = [col for col in ordem_colunas if col in df.columns]
149
- return df.select(ordem_colunas).sort("BondType", "MaturityDate")
150
-
151
-
152
- def _buscar_dados_tpf(date: dt.date) -> pl.DataFrame:
153
- """Busca e processa dados do mercado secundário de TPF diretamente da fonte ANBIMA.
154
-
155
- Esta é uma função de baixo nível para uso interno. Ela lida com a lógica
156
- de construir a URL correta (pública ou RTM), baixar os dados com novas
157
- tentativas e processá-los em um DataFrame estruturado.
158
-
159
- Args:
160
- date (dt.date): A data de referência para os dados.
161
-
162
- Returns:
163
- pl.DataFrame: Um DataFrame contendo os dados de mercado de títulos
164
- processados, ou um DataFrame vazio se os dados não estiverem
165
- disponíveis ou ocorrer um erro de conexão.
166
- """
167
- url_arquivo = _montar_url_arquivo(date)
168
- data_str = date.strftime("%d/%m/%Y")
169
-
170
- # --- "FAIL-FAST" PARA EVITAR RETRIES DESNECESSÁRIOS NA RTM ---
171
- if ANBIMA_RTM_URL in url_arquivo:
172
- try:
173
- # Tenta resolver o hostname da RTM. É uma verificação de rede rápida.
174
- socket.gethostbyname(ANBIMA_RTM_HOSTNAME)
175
- except socket.gaierror:
176
- # Se falhar (gaierror = get address info error), não estamos na RTM.
177
- # Não adianta prosseguir para a função com retry.
178
- logger.warning(
179
- f"Não foi possível resolver o host da RTM para {data_str}. "
180
- "Isso é esperado fora da rede RTM. Dados históricos exigem acesso "
181
- "à RTM. Retornando DataFrame vazio."
182
- )
183
- return pl.DataFrame()
184
-
185
- try:
186
- # Se passamos pela verificação da RTM, agora podemos chamar a função com retry.
187
- csv_texto = _obter_csv(date)
188
- if not csv_texto.strip():
189
- logger.info(
190
- f"Dados TPF de mercado secundário para {data_str} não disponíveis. "
191
- "Retornando DataFrame vazio."
192
- )
193
- return pl.DataFrame()
194
-
195
- df = _ler_csv(csv_texto)
196
- df = _processar_df_bruto(df)
197
-
198
- return df
199
-
200
- except HTTPError as e:
201
- if e.response.status_code == 404: # noqa
202
- logger.info(
203
- f"Dados TPF de mercado secundário para {data_str} (HTTP 404). "
204
- "Retornando DataFrame vazio."
205
- )
206
- return pl.DataFrame()
207
- logger.error(
208
- "Erro HTTP ao buscar dados para %s de %s: %s", data_str, url_arquivo, e
209
- )
210
- raise
211
-
212
- # Este bloco ainda é útil para outros URLErrors (ex: timeout genuíno na URL pública)
213
- except RequestException:
214
- logger.exception("RequestException ao buscar dados TPF para %s", data_str)
215
- raise
216
-
217
- except Exception:
218
- msg = f"Ocorreu um erro inesperado ao buscar dados TPF para {data_str}"
219
- logger.exception(msg)
220
- raise
221
-
222
-
223
- def tpf(
224
- date: DateLike,
225
- bond_type: BOND_TYPES | None = None,
226
- ) -> pl.DataFrame:
227
- """Recupera os dados do mercado secundário de TPF da ANBIMA.
228
-
229
- Esta função busca taxas indicativas e outros dados de títulos públicos
230
- brasileiros. Primeiro consulta o cache local; se não houver dados,
231
- busca diretamente na fonte (ANBIMA).
232
-
233
- Args:
234
- date (DateLike): A data de referência para os dados (ex: '2024-06-14').
235
- bond_type (str, optional): Filtra por tipo de título. Aceita os tipos
236
- individuais ('LTN', 'NTN-F', 'NTN-B', 'NTN-C', 'LFT') ou 'PRE'
237
- como atalho para prefixados ('LTN' e 'NTN-F').
238
-
239
- Returns:
240
- pl.DataFrame: Um DataFrame contendo os dados solicitados.
241
- Retorna um DataFrame vazio se não houver dados para a data especificada (ex:
242
- finais de semana, feriados ou datas futuras).
243
-
244
- Examples:
245
- >>> from pyield import anbima
246
- >>> df = anbima.tpf(date="06-02-2026")
247
-
248
- Data columns:
249
- - BondType: Tipo do título público (e.g., 'LTN', 'NTN-B').
250
- - ReferenceDate: Data de referência dos dados.
251
- - SelicCode: Código do título no SELIC.
252
- - IssueBaseDate: Data base ou de emissão do título.
253
- - MaturityDate: Data de vencimento do título.
254
- - Price: Preço Unitário (PU) calculado para liquidação em D0.
255
- - BidRate: Taxa de compra para liquidação em D0 (decimal).
256
- - AskRate: Taxa de venda para liquidação em D0 (decimal).
257
- - IndicativeRate: Taxa indicativa para liquidação em D0 (decimal).
258
-
259
- Notes:
260
- A fonte dos dados segue a seguinte hierarquia:
261
-
262
- 1. **Cache Local:** Fornece acesso rápido a dados históricos
263
- desde 01/01/2020.
264
- 2. **Fonte ANBIMA (fallback):** Se a data não estiver no cache,
265
- busca automaticamente na fonte. Para datas recentes (até 5 dias
266
- úteis), usa o site público. Para datas mais antigas, requer
267
- acesso à rede RTM.
268
-
269
- Para obter o dado completo direto da fonte (todas as colunas),
270
- use :func:`fetch_tpf`.
271
- """
272
- date = converter_datas(date)
273
-
274
- if not data_referencia_valida(date):
275
- return pl.DataFrame()
276
-
277
- # Cache primeiro; se não tiver, busca na fonte
278
- df = obter_dataset_cacheado("tpf")
279
- if not df.is_empty():
280
- df = df.filter(pl.col("ReferenceDate") == date)
281
- if df.is_empty():
282
- df = _buscar_dados_tpf(date)
283
- df = _selecionar_e_ordenar_colunas(df)
284
-
285
- if df.is_empty():
286
- return pl.DataFrame()
287
-
288
- if bond_type:
289
- norm_bond_type = _mapear_tipo_titulo(bond_type)
290
- df = df.filter(pl.col("BondType").is_in(norm_bond_type))
291
-
292
- return df.sort("ReferenceDate", "BondType", "MaturityDate")
293
-
294
-
295
- def fetch_tpf(
296
- date: DateLike,
297
- ) -> pl.DataFrame:
298
- """Busca os dados do mercado secundário de TPF direto da fonte ANBIMA.
299
-
300
- Retorna todas as colunas publicadas pela ANBIMA, sem cache e sem
301
- filtro de colunas. Indicado para uso em jobs e pipelines de dados.
302
-
303
- Args:
304
- date (DateLike): Data de referência (ex: '2024-06-14').
305
-
306
- Returns:
307
- pl.DataFrame: DataFrame com todas as colunas da ANBIMA.
308
- Retorna DataFrame vazio se não houver dados.
309
- """
310
- date = converter_datas(date)
311
-
312
- if not data_referencia_valida(date):
313
- return pl.DataFrame()
314
-
315
- df = _buscar_dados_tpf(date)
316
- if df.is_empty():
317
- return pl.DataFrame()
318
-
319
- return df.sort("ReferenceDate", "BondType", "MaturityDate")
320
-
321
-
322
- def tpf_maturities(
323
- date: DateLike,
324
- bond_type: BOND_TYPES,
325
- ) -> pl.Series:
326
- """Recupera os vencimentos existentes para um tipo de título na data especificada.
327
-
328
- Args:
329
- date (DateLike): A data de referência para os vencimentos.
330
- bond_type (BOND_TYPES): O tipo de título para filtrar (ex: 'PRE' para 'LTN'
331
- e 'NTN-F', ou especifique 'LTN' ou 'NTN-F' diretamente).
332
-
333
- Returns:
334
- pl.Series: Uma Series contendo as datas de vencimento únicas para o(s)
335
- tipo(s) de título especificado(s).
336
-
337
- Examples:
338
- >>> from pyield import anbima
339
- >>> anbima.tpf_maturities(date="22-08-2025", bond_type="PRE")
340
- shape: (18,)
341
- Series: 'MaturityDate' [date]
342
- [
343
- 2025-10-01
344
- 2026-01-01
345
- 2026-04-01
346
- 2026-07-01
347
- 2026-10-01
348
- …
349
- 2030-01-01
350
- 2031-01-01
351
- 2032-01-01
352
- 2033-01-01
353
- 2035-01-01
354
- ]
355
- """
356
- return tpf(date, bond_type)["MaturityDate"].unique().sort()
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