pyield 0.47.0__tar.gz → 0.47.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.
Files changed (72) hide show
  1. {pyield-0.47.0 → pyield-0.47.2}/PKG-INFO +1 -1
  2. pyield-0.47.2/pyield/__about__.py +1 -0
  3. {pyield-0.47.0 → pyield-0.47.2}/pyield/__init__.py +10 -12
  4. {pyield-0.47.0 → pyield-0.47.2}/pyield/_internal/data_cache.py +2 -6
  5. pyield-0.47.2/pyield/b3/__init__.py +27 -0
  6. pyield-0.47.0/pyield/b3/intraday_derivatives.py → pyield-0.47.2/pyield/b3/derivatives_intraday.py +12 -16
  7. {pyield-0.47.0 → pyield-0.47.2}/pyield/b3/di1.py +1 -1
  8. pyield-0.47.2/pyield/b3/futures/__init__.py +153 -0
  9. {pyield-0.47.0 → pyield-0.47.2}/pyield/b3/futures/common.py +4 -0
  10. {pyield-0.47.0 → pyield-0.47.2}/pyield/b3/futures/historical.py +136 -144
  11. pyield-0.47.2/pyield/b3/futures/intraday.py +116 -0
  12. {pyield-0.47.0 → pyield-0.47.2}/pyield/b3/price_report.py +70 -49
  13. {pyield-0.47.0 → pyield-0.47.2}/pyield/selic/cpm.py +2 -2
  14. pyield-0.47.0/pyield/__about__.py +0 -1
  15. pyield-0.47.0/pyield/b3/__init__.py +0 -15
  16. pyield-0.47.0/pyield/b3/futures/__init__.py +0 -178
  17. pyield-0.47.0/pyield/b3/futures/intraday.py +0 -160
  18. {pyield-0.47.0 → pyield-0.47.2}/.gitignore +0 -0
  19. {pyield-0.47.0 → pyield-0.47.2}/LICENSE +0 -0
  20. {pyield-0.47.0 → pyield-0.47.2}/README.md +0 -0
  21. {pyield-0.47.0 → pyield-0.47.2}/pyield/_internal/__init__.py +0 -0
  22. {pyield-0.47.0 → pyield-0.47.2}/pyield/_internal/br_numbers.py +0 -0
  23. {pyield-0.47.0 → pyield-0.47.2}/pyield/_internal/cache.py +0 -0
  24. {pyield-0.47.0 → pyield-0.47.2}/pyield/_internal/converters.py +0 -0
  25. {pyield-0.47.0 → pyield-0.47.2}/pyield/_internal/retry.py +0 -0
  26. {pyield-0.47.0 → pyield-0.47.2}/pyield/_internal/types.py +0 -0
  27. {pyield-0.47.0 → pyield-0.47.2}/pyield/anbima/__init__.py +0 -0
  28. {pyield-0.47.0 → pyield-0.47.2}/pyield/anbima/ettj_intraday.py +0 -0
  29. {pyield-0.47.0 → pyield-0.47.2}/pyield/anbima/ettj_last.py +0 -0
  30. {pyield-0.47.0 → pyield-0.47.2}/pyield/anbima/ima.py +0 -0
  31. {pyield-0.47.0 → pyield-0.47.2}/pyield/anbima/imaq.py +0 -0
  32. {pyield-0.47.0 → pyield-0.47.2}/pyield/anbima/tpf.py +0 -0
  33. {pyield-0.47.0 → pyield-0.47.2}/pyield/b3/_contracts.py +0 -0
  34. {pyield-0.47.0 → pyield-0.47.2}/pyield/b3/_validar_pregao.py +0 -0
  35. {pyield-0.47.0 → pyield-0.47.2}/pyield/b3/di_over.py +0 -0
  36. {pyield-0.47.0 → pyield-0.47.2}/pyield/bc/__init__.py +0 -0
  37. {pyield-0.47.0 → pyield-0.47.2}/pyield/bc/auction.py +0 -0
  38. {pyield-0.47.0 → pyield-0.47.2}/pyield/bc/copom.py +0 -0
  39. {pyield-0.47.0 → pyield-0.47.2}/pyield/bc/ptax_api.py +0 -0
  40. {pyield-0.47.0 → pyield-0.47.2}/pyield/bc/rates.py +0 -0
  41. {pyield-0.47.0 → pyield-0.47.2}/pyield/bc/repo.py +0 -0
  42. {pyield-0.47.0 → pyield-0.47.2}/pyield/bc/trades_intraday.py +0 -0
  43. {pyield-0.47.0 → pyield-0.47.2}/pyield/bc/trades_monthly.py +0 -0
  44. {pyield-0.47.0 → pyield-0.47.2}/pyield/bc/vna.py +0 -0
  45. {pyield-0.47.0 → pyield-0.47.2}/pyield/bday/__init__.py +0 -0
  46. {pyield-0.47.0 → pyield-0.47.2}/pyield/bday/core.py +0 -0
  47. {pyield-0.47.0 → pyield-0.47.2}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  48. {pyield-0.47.0 → pyield-0.47.2}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  49. {pyield-0.47.0 → pyield-0.47.2}/pyield/bday/holidays/brholidays.py +0 -0
  50. {pyield-0.47.0 → pyield-0.47.2}/pyield/clock.py +0 -0
  51. {pyield-0.47.0 → pyield-0.47.2}/pyield/fwd.py +0 -0
  52. {pyield-0.47.0 → pyield-0.47.2}/pyield/interpolator.py +0 -0
  53. {pyield-0.47.0 → pyield-0.47.2}/pyield/ipca/__init__.py +0 -0
  54. {pyield-0.47.0 → pyield-0.47.2}/pyield/ipca/historical.py +0 -0
  55. {pyield-0.47.0 → pyield-0.47.2}/pyield/ipca/projected.py +0 -0
  56. {pyield-0.47.0 → pyield-0.47.2}/pyield/py.typed +0 -0
  57. {pyield-0.47.0 → pyield-0.47.2}/pyield/rmd.py +0 -0
  58. {pyield-0.47.0 → pyield-0.47.2}/pyield/selic/__init__.py +0 -0
  59. {pyield-0.47.0 → pyield-0.47.2}/pyield/selic/probabilities.py +0 -0
  60. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/__init__.py +0 -0
  61. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/auctions.py +0 -0
  62. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/benchmark.py +0 -0
  63. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/lft.py +0 -0
  64. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/ltn.py +0 -0
  65. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/ntnb.py +0 -0
  66. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/ntnb1.py +0 -0
  67. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/ntnbprinc.py +0 -0
  68. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/ntnc.py +0 -0
  69. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/ntnf.py +0 -0
  70. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/pre.py +0 -0
  71. {pyield-0.47.0 → pyield-0.47.2}/pyield/tn/utils.py +0 -0
  72. {pyield-0.47.0 → pyield-0.47.2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyield
3
- Version: 0.47.0
3
+ Version: 0.47.2
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.47.2"
@@ -2,7 +2,7 @@ import logging
2
2
 
3
3
  from pyield import anbima, b3, bc, bday, ipca, selic, tn
4
4
  from pyield.__about__ import __version__
5
- from pyield.b3 import di1, futures, futures_intraday
5
+ from pyield.b3 import di1, futures
6
6
  from pyield.clock import now, today
7
7
  from pyield.fwd import forward, forwards
8
8
  from pyield.interpolator import Interpolator
@@ -11,33 +11,31 @@ from pyield.selic.cpm import data as copom_options
11
11
  from pyield.tn import lft, ltn, ntnb, ntnb1, ntnbprinc, ntnc, ntnf, pre
12
12
 
13
13
  __all__ = [
14
+ "Interpolator",
14
15
  "__version__",
15
16
  "anbima",
16
- "bc",
17
17
  "b3",
18
- "ipca",
19
- "tn",
18
+ "bc",
20
19
  "bday",
20
+ "copom_options",
21
21
  "di1",
22
- "forwards",
23
22
  "forward",
23
+ "forwards",
24
24
  "futures",
25
- "copom_options",
26
- "Interpolator",
27
- "today",
28
- "now",
29
25
  "lft",
30
26
  "ltn",
27
+ "now",
31
28
  "ntnb",
32
- "ntnbprinc",
33
29
  "ntnb1",
30
+ "ntnbprinc",
34
31
  "ntnc",
35
32
  "ntnf",
33
+ "ipca",
36
34
  "pre",
37
35
  "rmd",
38
- "bday",
39
36
  "selic",
40
- "futures_intraday",
37
+ "tn",
38
+ "today",
41
39
  ]
42
40
 
43
41
  # Configura o logger do pacote principal com um NullHandler
@@ -1,5 +1,4 @@
1
1
  import functools
2
- import io
3
2
  import logging
4
3
  from enum import Enum
5
4
  from typing import Literal
@@ -55,11 +54,8 @@ def _carregar_arquivo_github(url_arquivo: str) -> pl.DataFrame:
55
54
  # Garante que a requisição foi sucesso (200 OK), senão levanta erro
56
55
  response.raise_for_status()
57
56
 
58
- # 2. Transforma os bytes em um objeto de arquivo em memória
59
- file_buffer = io.BytesIO(response.content)
60
-
61
- # 3. O Polars lê o buffer como se fosse um arquivo local
62
- return pl.read_parquet(file_buffer)
57
+ # 2. O Polars o buffer como se fosse um arquivo local
58
+ return pl.read_parquet(response.content)
63
59
 
64
60
 
65
61
  @functools.lru_cache(maxsize=8)
@@ -0,0 +1,27 @@
1
+ from pyield.b3 import di1
2
+ from pyield.b3.derivatives_intraday import derivatives_intraday_fetch
3
+ from pyield.b3.di_over import di_over
4
+ from pyield.b3.futures import (
5
+ futures,
6
+ futures_available_dates,
7
+ futures_enrich,
8
+ futures_intraday,
9
+ )
10
+ from pyield.b3.price_report import (
11
+ price_report_extract,
12
+ price_report_fetch,
13
+ price_report_read,
14
+ )
15
+
16
+ __all__ = [
17
+ "di_over",
18
+ "di1",
19
+ "futures",
20
+ "futures_available_dates",
21
+ "futures_enrich",
22
+ "futures_intraday",
23
+ "derivatives_intraday_fetch",
24
+ "price_report_extract",
25
+ "price_report_fetch",
26
+ "price_report_read",
27
+ ]
@@ -35,7 +35,6 @@ import requests
35
35
  from pyield import clock
36
36
  from pyield._internal.cache import ttl_cache
37
37
  from pyield._internal.retry import retry_padrao
38
- from pyield.b3._validar_pregao import intraday_disponivel
39
38
 
40
39
  URL_BASE_INTRADAY = "https://cotacao.b3.com.br/mds/api/v1/DerivativeQuotation"
41
40
 
@@ -111,7 +110,7 @@ def _processar_colunas_intraday(df: pl.DataFrame) -> pl.DataFrame:
111
110
  )
112
111
 
113
112
 
114
- def fetch_intraday_derivatives(contract_code: str) -> pl.DataFrame:
113
+ def derivatives_intraday_fetch(contract_code: str) -> pl.DataFrame:
115
114
  """Busca cotações intraday brutas de derivativos da B3.
116
115
 
117
116
  Faz a chamada ao endpoint ``DerivativeQuotation`` e devolve um DataFrame
@@ -123,7 +122,8 @@ def fetch_intraday_derivatives(contract_code: str) -> pl.DataFrame:
123
122
  derivados devem ser feitos no módulo consumidor.
124
123
 
125
124
  Args:
126
- contract_code: Código base do derivativo na B3.
125
+ contract_code: Código base do derivativo na B3
126
+ (ex.: ``DI1``, ``DOL``, ``DAP``, ``DDI``, ``FRC``, ``FRO``, ``IND``).
127
127
 
128
128
  Returns:
129
129
  DataFrame Polars com o payload normalizado da API.
@@ -150,21 +150,17 @@ def fetch_intraday_derivatives(contract_code: str) -> pl.DataFrame:
150
150
  * preco_oferta_compra (Float64): melhor oferta de compra (opcional).
151
151
  * preco_oferta_venda (Float64): melhor oferta de venda (opcional).
152
152
  * tipo_lado (String): tipo de lado (opcional).
153
- * atualizado_as (Datetime): horário aproximado a que o dado se
154
- refere (horário da consulta menos 15 min de atraso da fonte).
153
+ * horario_referencia (Time): horário aproximado a que os
154
+ dados se referem. A fonte intraday da B3 possui atraso de
155
+ ~15 min; este valor é calculado subtraindo esse atraso do
156
+ horário da consulta.
155
157
  """
156
- if not intraday_disponivel():
157
- return pl.DataFrame()
158
-
159
158
  dados_json = _buscar_json_intraday(contract_code)
160
159
  if not dados_json:
161
160
  return pl.DataFrame()
162
161
 
163
- return (
164
- _converter_json_intraday(dados_json)
165
- .pipe(_processar_colunas_intraday)
166
- .with_columns(
167
- atualizado_as=clock.now() - dt.timedelta(minutes=15),
168
- )
169
- .sort("codigo_negociacao")
170
- )
162
+ df = _converter_json_intraday(dados_json)
163
+ df = _processar_colunas_intraday(df)
164
+ horario = (clock.now() - dt.timedelta(minutes=15)).time()
165
+ df = df.with_columns(horario_referencia=horario)
166
+ return df.sort("codigo_negociacao")
@@ -4,7 +4,7 @@ import pyield._internal.converters as cv
4
4
  from pyield import b3, bday, interpolator
5
5
  from pyield._internal.data_cache import obter_dataset_cacheado
6
6
  from pyield._internal.types import ArrayLike, DateLike, any_is_collection, any_is_empty
7
- from pyield.b3.futures import available_dates as _listar_datas
7
+ from pyield.b3.futures import futures_available_dates as _listar_datas
8
8
 
9
9
 
10
10
  def data(
@@ -0,0 +1,153 @@
1
+ import polars as pl
2
+
3
+ import pyield._internal.converters as cv
4
+ from pyield._internal.types import ArrayLike, DateLike, any_is_empty
5
+ from pyield.b3._validar_pregao import data_negociacao_valida
6
+ from pyield.b3.futures import historical, intraday
7
+
8
+
9
+ def futures_enrich(
10
+ df: pl.DataFrame,
11
+ contract_code: str,
12
+ ) -> pl.DataFrame:
13
+ """Enriquece DataFrame bruto do Price Report (PR) da B3.
14
+
15
+ Aceita um DataFrame com colunas no schema original da B3
16
+ (ex.: ``TradDt``, ``TckrSymb``) ou já renomeadas para o padrão
17
+ PYield. Adiciona data de vencimento, dias úteis/corridos e
18
+ colunas derivadas (dv01, taxa_forward) conforme o contrato.
19
+
20
+ Args:
21
+ df: DataFrame com dados do PR da B3.
22
+ contract_code: Código do contrato futuro
23
+ (ex.: "DI1", "DOL").
24
+
25
+ Returns:
26
+ DataFrame Polars enriquecido e ordenado.
27
+ """
28
+ return historical.enrich(df, contract_code)
29
+
30
+
31
+ def futures(
32
+ date: DateLike | ArrayLike,
33
+ contract_code: str,
34
+ ) -> pl.DataFrame:
35
+ """Busca dados de um contrato futuro da B3 para a data de referência.
36
+
37
+ Dados obtidos do dataset PR cacheado no GitHub (disponível desde 2018).
38
+ Para dados do pregão corrente, use ``futures_intraday``.
39
+
40
+ Args:
41
+ date: Data de referência para consulta ou coleção de datas.
42
+ Quando uma coleção é fornecida, os dados são buscados para cada
43
+ data individualmente e concatenados. Datas inválidas (feriados,
44
+ fins de semana, futuras) são silenciosamente ignoradas.
45
+ contract_code: Código do contrato futuro na B3. Contratos
46
+ disponíveis no cache histórico:
47
+ - Juros: DI1, DDI, FRC, FRO, DAP
48
+ - Moedas: DOL, WDO
49
+ - Índices: IND, WIN
50
+
51
+ Returns:
52
+ DataFrame Polars com os dados do contrato informado.
53
+
54
+ Examples:
55
+ >>> df = futures("31-05-2024", "DI1")
56
+ >>> df = futures("31-05-2024", "DAP")
57
+
58
+ Lista de datas:
59
+
60
+ >>> df = futures(["29-05-2024", "31-05-2024"], "DI1")
61
+ >>> df["data_referencia"].unique().sort().to_list()
62
+ [datetime.date(2024, 5, 29), datetime.date(2024, 5, 31)]
63
+
64
+ Véspera de Natal e Ano Novo não têm pregão:
65
+
66
+ >>> futures("24-12-2024", "DI1").is_empty()
67
+ True
68
+ >>> futures("31-12-2024", "DI1").is_empty()
69
+ True
70
+
71
+ Data futura e fim de semana retornam DataFrame vazio:
72
+
73
+ >>> import datetime as dt
74
+ >>> amanha = dt.date.today() + dt.timedelta(days=1)
75
+ >>> futures(amanha, "DI1").is_empty()
76
+ True
77
+ >>> futures("04-01-2025", "DI1").is_empty() # sábado
78
+ True
79
+
80
+ """
81
+ if any_is_empty(date, contract_code):
82
+ return pl.DataFrame()
83
+
84
+ dados_convertidos = cv.converter_datas(date)
85
+ if isinstance(dados_convertidos, pl.Series):
86
+ datas_validas = []
87
+ for d in dados_convertidos:
88
+ if d is not None and data_negociacao_valida(d):
89
+ datas_validas.append(d)
90
+ return historical._buscar_do_cache(datas_validas, contract_code)
91
+
92
+ if not data_negociacao_valida(dados_convertidos):
93
+ return pl.DataFrame()
94
+
95
+ return historical.historical(dados_convertidos, contract_code)
96
+
97
+
98
+ def futures_intraday(
99
+ contract_code: str,
100
+ ) -> pl.DataFrame:
101
+ """Busca dados intraday de contratos futuros da B3.
102
+
103
+ Retorna os dados mais recentes do pregão corrente, com atraso
104
+ aproximado de 15 minutos. Para dados históricos consolidados,
105
+ use ``futures``.
106
+
107
+ Args:
108
+ contract_code: Código do contrato futuro na B3
109
+ (ex.: 'DI1', 'DAP', 'DOL').
110
+
111
+ Returns:
112
+ DataFrame Polars com dados intraday. Retorna DataFrame vazio
113
+ fora do horário de pregão.
114
+
115
+ Notes:
116
+ As colunas com prefixo ``preco_`` aparecem para contratos cotados
117
+ por preço (ex.: DOL, IND). As com prefixo ``taxa_`` aparecem para
118
+ contratos cotados por taxa (ex.: DI1, DAP, DDI, FRC, FRO).
119
+ """
120
+ if not contract_code:
121
+ return pl.DataFrame()
122
+
123
+ return intraday.intraday(contract_code)
124
+
125
+
126
+ def futures_available_dates(contract_code: str) -> pl.Series:
127
+ """Retorna as datas de negociação disponíveis no dataset cacheado.
128
+
129
+ Args:
130
+ contract_code: Código do contrato futuro na B3 (ex.: DI1, DOL).
131
+
132
+ Returns:
133
+ Series ordenada de datas (Date) para as quais há dados de ajuste.
134
+
135
+ Examples:
136
+ >>> from pyield.b3.futures import futures_available_dates
137
+ >>> futures_available_dates("DI1").head(3)
138
+ shape: (3,)
139
+ Series: 'data_referencia' [date]
140
+ [
141
+ 2018-01-02
142
+ 2018-01-03
143
+ 2018-01-04
144
+ ]
145
+ """
146
+ return historical.listar_datas_disponiveis(contract_code)
147
+
148
+
149
+ __all__ = [
150
+ "futures",
151
+ "futures_available_dates",
152
+ "futures_intraday",
153
+ ]
@@ -2,6 +2,10 @@ import polars as pl
2
2
 
3
3
  from pyield import bday
4
4
 
5
+ # Lista de contratos que negociam por taxa (juros/cupom).
6
+ # Nestes contratos, as colunas OHLC são taxas e precisam ser divididas por 100.
7
+ CONTRATOS_TAXA = {"DI1", "DAP", "DDI", "FRC", "FRO"}
8
+
5
9
  _MAPA_MESES: dict[str, int] = {
6
10
  "F": 1,
7
11
  "G": 2,
@@ -1,17 +1,13 @@
1
1
  import datetime as dt
2
2
 
3
3
  import polars as pl
4
+ import polars.selectors as cs
4
5
 
5
6
  from pyield import bday
6
7
  from pyield._internal.data_cache import obter_dataset_cacheado
7
- from pyield.b3._contracts import normalizar_codigos_contrato
8
- from pyield.b3.futures.common import adicionar_vencimento, expr_dv01
8
+ from pyield.b3.futures.common import CONTRATOS_TAXA, adicionar_vencimento, expr_dv01
9
9
  from pyield.fwd import forwards
10
10
 
11
- # Lista de contratos que negociam por taxa (juros/cupom).
12
- # Nestes contratos, as colunas OHLC são taxas e precisam ser divididas por 100.
13
- CONTRATOS_TAXA = {"DI1", "DAP", "DDI", "FRC", "FRO"}
14
-
15
11
  # Renomeação preco_* → taxa_* para contratos cotados por taxa.
16
12
  # Bid/Ask são invertidos: BestBidPric (bid em PU) = menor taxa = venda de taxa;
17
13
  # BestAskPric (ask em PU) = maior taxa = compra de taxa.
@@ -27,6 +23,55 @@ _PRECO_PARA_TAXA = {
27
23
  "preco_limite_maximo": "taxa_limite_maximo",
28
24
  }
29
25
 
26
+ # Colunas de saída para contratos cotados por preço (DOL, WDO, IND, WIN).
27
+ _COLUNAS_CONTRATO_PRECO = (
28
+ "data_referencia",
29
+ "codigo_negociacao",
30
+ "data_vencimento",
31
+ "dias_uteis",
32
+ "dias_corridos",
33
+ "contratos_abertos",
34
+ "numero_negocios",
35
+ "volume_negociado",
36
+ "volume_financeiro",
37
+ "preco_limite_minimo",
38
+ "preco_limite_maximo",
39
+ "preco_abertura",
40
+ "preco_minimo",
41
+ "preco_maximo",
42
+ "preco_medio",
43
+ "preco_fechamento",
44
+ "preco_ultima_oferta_compra",
45
+ "preco_ultima_oferta_venda",
46
+ "preco_ajuste",
47
+ )
48
+
49
+ # Colunas de saída para contratos cotados por taxa (DI1, DAP, DDI, FRC, FRO).
50
+ _COLUNAS_CONTRATO_TAXA = (
51
+ "data_referencia",
52
+ "codigo_negociacao",
53
+ "data_vencimento",
54
+ "dias_uteis",
55
+ "dias_corridos",
56
+ "dv01",
57
+ "contratos_abertos",
58
+ "numero_negocios",
59
+ "volume_negociado",
60
+ "volume_financeiro",
61
+ "preco_ajuste",
62
+ "taxa_limite_minimo",
63
+ "taxa_limite_maximo",
64
+ "taxa_abertura",
65
+ "taxa_minima",
66
+ "taxa_maxima",
67
+ "taxa_media",
68
+ "taxa_fechamento",
69
+ "taxa_ultima_oferta_venda",
70
+ "taxa_ultima_oferta_compra",
71
+ "taxa_ajuste",
72
+ "taxa_forward",
73
+ )
74
+
30
75
  # Normaliza o schema XML bruto da B3 para o padrão de colunas deste módulo.
31
76
  _RENOMEAR_COLUNAS_PR = {
32
77
  "TradDt": "data_referencia",
@@ -49,25 +94,95 @@ _RENOMEAR_COLUNAS_PR = {
49
94
  }
50
95
 
51
96
 
52
- def _normalizar_colunas_pr(df: pl.DataFrame) -> pl.DataFrame:
53
- return df.rename({k: v for k, v in _RENOMEAR_COLUNAS_PR.items() if k in df.columns})
97
+ def _obter_cache_filtrado(codigo_contrato: str) -> pl.DataFrame:
98
+ """Carrega o dataset PR cacheado e filtra por contrato."""
99
+ return obter_dataset_cacheado("futures").filter(
100
+ pl.col("TckrSymb").str.starts_with(codigo_contrato)
101
+ )
102
+
103
+
104
+ def _enriquecer_dados(df: pl.DataFrame, codigo_contrato: str) -> pl.DataFrame:
105
+ df = df.with_columns(
106
+ dias_uteis=bday.count_expr("data_referencia", "data_vencimento"),
107
+ dias_corridos=(
108
+ pl.col("data_vencimento") - pl.col("data_referencia")
109
+ ).dt.total_days(),
110
+ ).filter(pl.col("dias_corridos") > 0)
111
+
112
+ eh_taxa = codigo_contrato in CONTRATOS_TAXA
113
+ if eh_taxa:
114
+ df = df.rename(_PRECO_PARA_TAXA, strict=False)
115
+ df = df.with_columns(cs.starts_with("taxa_").truediv(100).round(6))
116
+
117
+ if codigo_contrato == "DI1":
118
+ df = df.with_columns(
119
+ dv01=expr_dv01("dias_uteis", "taxa_ajuste", "preco_ajuste")
120
+ )
121
+
122
+ if codigo_contrato in {"DI1", "DAP"}:
123
+ df = df.with_columns(
124
+ taxa_forward=forwards(bdays=df["dias_uteis"], rates=df["taxa_ajuste"])
125
+ )
126
+
127
+ return df
54
128
 
55
129
 
56
- def _obter_pr_normalizado() -> pl.DataFrame:
57
- """Carrega o dataset PR e normaliza nomes de colunas."""
58
- df = obter_dataset_cacheado("futures")
59
- return _normalizar_colunas_pr(df)
130
+ def _selecionar_colunas_saida(df: pl.DataFrame, codigo_contrato: str) -> pl.DataFrame:
131
+ if codigo_contrato in CONTRATOS_TAXA:
132
+ colunas = _COLUNAS_CONTRATO_TAXA
133
+ else:
134
+ colunas = _COLUNAS_CONTRATO_PRECO
135
+ return df.select(c for c in colunas if c in df.columns)
136
+
137
+
138
+ def _buscar_do_cache(datas: list[dt.date], codigo_contrato: str) -> pl.DataFrame:
139
+ """Carrega histórico de futuros do dataset PR para uma lista de datas."""
140
+ if not datas:
141
+ return pl.DataFrame()
142
+
143
+ df = _obter_cache_filtrado(codigo_contrato)
144
+ df = df.filter(pl.col("TradDt").is_in(datas))
145
+ if df.is_empty():
146
+ return pl.DataFrame()
147
+
148
+ return enrich(df, codigo_contrato)
149
+
150
+
151
+ def enrich(df: pl.DataFrame, codigo_contrato: str) -> pl.DataFrame:
152
+ """Enriquece DataFrame bruto do Price Report (PR) da B3.
153
+
154
+ Aceita um DataFrame com colunas no schema original da B3 (ex.:
155
+ ``TradDt``, ``TckrSymb``) ou já renomeadas para o padrão PYield.
156
+ Adiciona data de vencimento, dias úteis/corridos e colunas derivadas
157
+ (dv01, taxa_forward) conforme o contrato.
158
+
159
+ Args:
160
+ df: DataFrame com dados do PR da B3.
161
+ codigo_contrato: Código do contrato futuro (ex.: "DI1", "DOL").
162
+
163
+ Returns:
164
+ DataFrame Polars enriquecido e ordenado.
165
+ """
166
+ if df.is_empty():
167
+ return pl.DataFrame()
168
+
169
+ df = df.rename(_RENOMEAR_COLUNAS_PR)
170
+ df = adicionar_vencimento(df, codigo_contrato, coluna_ticker="codigo_negociacao")
171
+ df = _enriquecer_dados(df, codigo_contrato)
172
+ df = _selecionar_colunas_saida(df, codigo_contrato)
173
+
174
+ return df.sort("data_referencia", "data_vencimento")
60
175
 
61
176
 
62
177
  def historical(
63
178
  data: dt.date,
64
- codigo_contrato: str | list[str],
179
+ codigo_contrato: str,
65
180
  ) -> pl.DataFrame:
66
181
  """Busca histórico de futuros no dataset PR cacheado.
67
182
 
68
183
  Args:
69
184
  data: Data de negociação.
70
- codigo_contrato: Código(s) do contrato futuro na B3.
185
+ codigo_contrato: Código do contrato futuro na B3.
71
186
 
72
187
  Returns:
73
188
  DataFrame Polars com dados históricos de futuros.
@@ -113,10 +228,10 @@ def historical(
113
228
  Usa exclusivamente o dataset PR cacheado no GitHub. Contratos
114
229
  disponíveis: DI1, DDI, FRC, FRO, DAP, DOL, WDO, IND, WIN.
115
230
 
116
- As colunas com prefixo ``preco_`` aparecem para contratos cotados por
117
- preço (ex.: DOL, IND). As com prefixo ``taxa_`` aparecem para contratos
118
- cotados por taxa (ex.: DI1, DAP, DDI, FRC, FRO). Nem todas as colunas
119
- estarão presentes em todos os contratos.
231
+ As colunas com prefixo ``preco_`` aparecem para contratos cotados
232
+ por preço (ex.: DOL, IND). As com prefixo ``taxa_`` aparecem para
233
+ contratos cotados por taxa (ex.: DI1, DAP, DDI, FRC, FRO). Nem
234
+ todas as colunas estarão presentes em todos os contratos.
120
235
 
121
236
  ``*_fechamento`` é o último negócio realizado (last trade).
122
237
  ``*_ultima_oferta_*`` é o bid/ask ao fim do pregão — não
@@ -125,139 +240,16 @@ def historical(
125
240
  ``taxa_ultima_oferta_compra`` = maior taxa (ask em PU),
126
241
  ``taxa_ultima_oferta_venda`` = menor taxa (bid em PU).
127
242
  """
128
- codigos = normalizar_codigos_contrato(codigo_contrato)
129
- if not codigos:
130
- return pl.DataFrame()
131
-
132
- dataframes = [
133
- df
134
- for codigo in codigos
135
- if not (df := _obter_futuros_pr([data], codigo)).is_empty()
136
- ]
137
-
138
- if not dataframes:
139
- return pl.DataFrame()
140
-
141
- if len(dataframes) == 1:
142
- return dataframes[0]
143
-
144
- df_resultado = pl.concat(dataframes, how="diagonal_relaxed")
145
- colunas_ordenacao = [
146
- coluna
147
- for coluna in ["data_referencia", "codigo_negociacao", "data_vencimento"]
148
- if coluna in df_resultado.columns
149
- ]
150
- if not colunas_ordenacao:
151
- return df_resultado
152
-
153
- return df_resultado.sort(*colunas_ordenacao)
154
-
155
-
156
- def _obter_futuros_pr(datas: list[dt.date], codigo_contrato: str) -> pl.DataFrame:
157
- """Carrega histórico de futuros do dataset PR para uma lista de datas."""
158
- if not datas:
159
- return pl.DataFrame()
160
-
161
- df = _obter_pr_normalizado()
162
- df = _filtrar_e_renomear(df, datas, codigo_contrato)
163
- if df.is_empty():
164
- return pl.DataFrame()
165
-
166
- df = adicionar_vencimento(df, codigo_contrato, coluna_ticker="codigo_negociacao")
167
- df = _enriquecer_dados(df, codigo_contrato)
168
- df = _selecionar_colunas_saida(df)
169
-
170
- return df.sort("data_referencia", "data_vencimento")
243
+ return _buscar_do_cache([data], codigo_contrato)
171
244
 
172
245
 
173
246
  def listar_datas_disponiveis(codigo_contrato: str) -> pl.Series:
174
247
  """Lista datas disponíveis no dataset PR para um contrato futuro."""
175
248
  return (
176
- _obter_pr_normalizado()
177
- .filter(pl.col("codigo_negociacao").str.starts_with(codigo_contrato))
178
- .get_column("data_referencia")
249
+ _obter_cache_filtrado(codigo_contrato)
250
+ .get_column("TradDt")
179
251
  .drop_nulls()
180
252
  .unique()
181
253
  .sort()
182
254
  .alias("data_referencia")
183
255
  )
184
-
185
-
186
- def _filtrar_e_renomear(
187
- df: pl.DataFrame, datas: list[dt.date], codigo_contrato: str
188
- ) -> pl.DataFrame:
189
- return df.filter(
190
- pl.col("data_referencia").is_in(datas),
191
- pl.col("codigo_negociacao").str.starts_with(codigo_contrato),
192
- )
193
-
194
-
195
- def _enriquecer_dados(df: pl.DataFrame, codigo_contrato: str) -> pl.DataFrame:
196
- df = df.with_columns(
197
- dias_uteis=bday.count_expr("data_referencia", "data_vencimento"),
198
- dias_corridos=(
199
- pl.col("data_vencimento") - pl.col("data_referencia")
200
- ).dt.total_days(),
201
- ).filter(pl.col("dias_corridos") > 0)
202
-
203
- eh_taxa = codigo_contrato in CONTRATOS_TAXA
204
- if eh_taxa:
205
- df = df.rename({k: v for k, v in _PRECO_PARA_TAXA.items() if k in df.columns})
206
-
207
- if eh_taxa:
208
- colunas_taxa = [c for c in df.columns if c.startswith("taxa_")]
209
- df = df.with_columns(pl.col(colunas_taxa).truediv(100).round(6))
210
-
211
- if (
212
- codigo_contrato == "DI1"
213
- and "preco_ajuste" in df.columns
214
- and "taxa_ajuste" in df.columns
215
- ):
216
- df = df.with_columns(
217
- dv01=expr_dv01("dias_uteis", "taxa_ajuste", "preco_ajuste")
218
- )
219
-
220
- if codigo_contrato in {"DI1", "DAP"} and "taxa_ajuste" in df.columns:
221
- df = df.with_columns(
222
- taxa_forward=forwards(bdays=df["dias_uteis"], rates=df["taxa_ajuste"])
223
- )
224
-
225
- return df
226
-
227
-
228
- def _selecionar_colunas_saida(df: pl.DataFrame) -> pl.DataFrame:
229
- ordem_preferida = [
230
- "data_referencia",
231
- "codigo_negociacao",
232
- "data_vencimento",
233
- "dias_uteis",
234
- "dias_corridos",
235
- "dv01",
236
- "contratos_abertos",
237
- "numero_negocios",
238
- "volume_negociado",
239
- "volume_financeiro",
240
- "preco_limite_minimo",
241
- "preco_limite_maximo",
242
- "preco_abertura",
243
- "preco_minimo",
244
- "preco_maximo",
245
- "preco_medio",
246
- "preco_fechamento",
247
- "preco_ultima_oferta_compra",
248
- "preco_ultima_oferta_venda",
249
- "preco_ajuste",
250
- "taxa_limite_minimo",
251
- "taxa_limite_maximo",
252
- "taxa_abertura",
253
- "taxa_minima",
254
- "taxa_maxima",
255
- "taxa_media",
256
- "taxa_fechamento",
257
- "taxa_ultima_oferta_venda",
258
- "taxa_ultima_oferta_compra",
259
- "taxa_ajuste",
260
- "taxa_forward",
261
- ]
262
- colunas_existentes = [c for c in ordem_preferida if c in df.columns]
263
- return df.select(colunas_existentes)