PYield 0.43.8__tar.gz → 0.44.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyield-0.43.8 → pyield-0.44.0}/PKG-INFO +1 -1
- pyield-0.44.0/pyield/__about__.py +1 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/__init__.py +2 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/_internal/data_cache.py +4 -4
- {pyield-0.43.8 → pyield-0.44.0}/pyield/b3/__init__.py +2 -0
- pyield-0.44.0/pyield/b3/_contracts.py +56 -0
- pyield-0.44.0/pyield/b3/_validar_pregao.py +36 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/b3/di1.py +41 -50
- pyield-0.44.0/pyield/b3/futures/__init__.py +128 -0
- pyield-0.44.0/pyield/b3/futures/common.py +72 -0
- pyield-0.44.0/pyield/b3/futures/historical.py +238 -0
- pyield-0.44.0/pyield/b3/futures/intraday.py +158 -0
- pyield-0.44.0/pyield/b3/intraday_derivatives.py +137 -0
- pyield-0.44.0/pyield/b3/price_report.py +423 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/selic/cpm.py +4 -5
- pyield-0.43.8/pyield/__about__.py +0 -1
- pyield-0.43.8/pyield/b3/common.py +0 -161
- pyield-0.43.8/pyield/b3/futures/__init__.py +0 -5
- pyield-0.43.8/pyield/b3/futures/core.py +0 -127
- pyield-0.43.8/pyield/b3/futures/historical/__init__.py +0 -1
- pyield-0.43.8/pyield/b3/futures/historical/core.py +0 -42
- pyield-0.43.8/pyield/b3/futures/historical/historical_b3.py +0 -222
- pyield-0.43.8/pyield/b3/futures/historical/historical_bmf.py +0 -314
- pyield-0.43.8/pyield/b3/futures/intraday.py +0 -204
- pyield-0.43.8/pyield/b3/price_report.py +0 -439
- {pyield-0.43.8 → pyield-0.44.0}/.gitignore +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/LICENSE +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/README.md +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/_internal/__init__.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/_internal/converters.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/_internal/retry.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/_internal/types.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/anbima/__init__.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/anbima/difusao.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/anbima/ettj_intraday.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/anbima/ettj_last.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/anbima/ima.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/anbima/imaq.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/anbima/tpf.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/b3/di_over.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bc/__init__.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bc/auction.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bc/copom.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bc/ptax_api.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bc/rates.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bc/repo.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bc/trades_intraday.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bc/trades_monthly.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bc/vna.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bday/__init__.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bday/core.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bday/holidays/br_holidays_new.txt +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bday/holidays/br_holidays_old.txt +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/bday/holidays/brholidays.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/clock.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/fwd.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/interpolator.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/ipca/__init__.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/ipca/historical.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/ipca/projected.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/py.typed +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/rmd.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/selic/__init__.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/__init__.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/auctions.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/benchmark.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/lft.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/ltn.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/ntnb.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/ntnb1.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/ntnbprinc.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/ntnc.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/ntnf.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/pre.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyield/tn/tools.py +0 -0
- {pyield-0.43.8 → pyield-0.44.0}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyield
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.44.0
|
|
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.44.0"
|
|
@@ -7,6 +7,7 @@ from pyield.clock import now, today
|
|
|
7
7
|
from pyield.fwd import forward, forwards
|
|
8
8
|
from pyield.interpolator import Interpolator
|
|
9
9
|
from pyield.rmd import rmd
|
|
10
|
+
from pyield.selic.cpm import data as copom_options
|
|
10
11
|
from pyield.tn import lft, ltn, ntnb, ntnb1, ntnbprinc, ntnc, ntnf, pre
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
@@ -21,6 +22,7 @@ __all__ = [
|
|
|
21
22
|
"forwards",
|
|
22
23
|
"forward",
|
|
23
24
|
"futures",
|
|
25
|
+
"copom_options",
|
|
24
26
|
"Interpolator",
|
|
25
27
|
"today",
|
|
26
28
|
"now",
|
|
@@ -16,8 +16,8 @@ registro = logging.getLogger(__name__)
|
|
|
16
16
|
|
|
17
17
|
# Estrutura interna única — usuário não vê isso
|
|
18
18
|
class _Dataset(Enum):
|
|
19
|
-
DI1 = ("b3_di.parquet", "TradeDate", "Futuro de DI (B3)")
|
|
20
19
|
TPF = ("anbima_tpf.parquet", "ReferenceDate", "TPF (ANBIMA)")
|
|
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):
|
|
23
23
|
self.nome_arquivo = nome_arquivo
|
|
@@ -25,7 +25,7 @@ class _Dataset(Enum):
|
|
|
25
25
|
self.descricao = descricao
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
type IdDataset = Literal["
|
|
28
|
+
type IdDataset = Literal["tpf", "pr"]
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def _obter_chave_data_hoje() -> str:
|
|
@@ -37,7 +37,7 @@ def _validar_id_dataset(id_dataset: str) -> _Dataset:
|
|
|
37
37
|
try:
|
|
38
38
|
return _Dataset[dataset_normalizado.upper()]
|
|
39
39
|
except KeyError as e:
|
|
40
|
-
msg = f"id_dataset inválido: '{id_dataset}'. Valores aceitos: '
|
|
40
|
+
msg = f"id_dataset inválido: '{id_dataset}'. Valores aceitos: 'tpf', 'pr'."
|
|
41
41
|
raise ValueError(msg) from e
|
|
42
42
|
|
|
43
43
|
|
|
@@ -81,7 +81,7 @@ def obter_dataset_cacheado(id_dataset: IdDataset) -> pl.DataFrame:
|
|
|
81
81
|
Obtém um dataset pelo ID. Cache expira diariamente.
|
|
82
82
|
|
|
83
83
|
Args:
|
|
84
|
-
id_dataset: "
|
|
84
|
+
id_dataset: "tpf" ou "pr"
|
|
85
85
|
"""
|
|
86
86
|
df = _obter_dataset_com_ttl(id_dataset.lower(), _obter_chave_data_hoje())
|
|
87
87
|
return df.clone()
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from pyield.b3 import di1
|
|
2
2
|
from pyield.b3.di_over import di_over
|
|
3
3
|
from pyield.b3.futures import futures
|
|
4
|
+
from pyield.b3.intraday_derivatives import fetch_intraday_derivatives
|
|
4
5
|
from pyield.b3.price_report import fetch_price_report, read_price_report
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
8
|
+
"fetch_intraday_derivatives",
|
|
7
9
|
"di_over",
|
|
8
10
|
"futures",
|
|
9
11
|
"fetch_price_report",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
|
|
5
|
+
from pyield._internal.types import any_is_empty
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def normalizar_codigo_contrato(contract_code: str | object) -> str:
|
|
9
|
+
"""Normaliza um código de contrato único para caixa alta.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
contract_code: Código único de contrato.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
str: Código normalizado em caixa alta ou string vazia se vazio.
|
|
16
|
+
"""
|
|
17
|
+
if any_is_empty(contract_code):
|
|
18
|
+
return ""
|
|
19
|
+
|
|
20
|
+
codigo = str(contract_code).strip().upper()
|
|
21
|
+
return codigo
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def normalizar_codigos_contrato(
|
|
25
|
+
contract_code: str | Sequence[str] | pl.Series,
|
|
26
|
+
) -> list[str]:
|
|
27
|
+
"""Normaliza códigos de contrato para lista única em caixa alta.
|
|
28
|
+
|
|
29
|
+
Remove valores vazios e duplicados, preservando a ordem de entrada.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
contract_code: Código único ou coleção de códigos de contrato.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
list[str]: Lista normalizada de códigos em caixa alta.
|
|
36
|
+
"""
|
|
37
|
+
if isinstance(contract_code, str):
|
|
38
|
+
codigo = normalizar_codigo_contrato(contract_code)
|
|
39
|
+
return [codigo] if codigo else []
|
|
40
|
+
|
|
41
|
+
if isinstance(contract_code, pl.Series):
|
|
42
|
+
valores = contract_code.to_list()
|
|
43
|
+
else:
|
|
44
|
+
valores = list(contract_code)
|
|
45
|
+
|
|
46
|
+
codigos_unicos: list[str] = []
|
|
47
|
+
codigos_vistos: set[str] = set()
|
|
48
|
+
for valor in valores:
|
|
49
|
+
codigo = normalizar_codigo_contrato(valor)
|
|
50
|
+
if not codigo:
|
|
51
|
+
continue
|
|
52
|
+
if codigo not in codigos_vistos:
|
|
53
|
+
codigos_vistos.add(codigo)
|
|
54
|
+
codigos_unicos.append(codigo)
|
|
55
|
+
|
|
56
|
+
return codigos_unicos
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from pyield import bday, clock
|
|
5
|
+
|
|
6
|
+
registro = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def data_negociacao_valida(data_negociacao: dt.date) -> bool:
|
|
10
|
+
"""Valida se a data de referência é utilizável para consulta.
|
|
11
|
+
|
|
12
|
+
Critérios:
|
|
13
|
+
- Deve ser um dia útil brasileiro.
|
|
14
|
+
- Não pode estar no futuro (maior que a data corrente no Brasil).
|
|
15
|
+
|
|
16
|
+
Retorna True se válida, False caso contrário (e loga um aviso).
|
|
17
|
+
"""
|
|
18
|
+
if data_negociacao > clock.today():
|
|
19
|
+
registro.warning(f"A data informada {data_negociacao} está no futuro.")
|
|
20
|
+
return False
|
|
21
|
+
if not bday.is_business_day(data_negociacao):
|
|
22
|
+
registro.warning(f"A data informada {data_negociacao} não é dia útil.")
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
# 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
|
|
29
|
+
}
|
|
30
|
+
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
|
+
return False
|
|
35
|
+
|
|
36
|
+
return True
|
|
@@ -7,10 +7,16 @@ import pyield._internal.converters as cv
|
|
|
7
7
|
from pyield import b3, bday, interpolator
|
|
8
8
|
from pyield._internal.data_cache import obter_dataset_cacheado
|
|
9
9
|
from pyield._internal.types import ArrayLike, DateLike, any_is_collection, any_is_empty
|
|
10
|
+
from pyield.b3.futures import historical as futuros_historico
|
|
10
11
|
|
|
11
12
|
registro = logging.getLogger(__name__)
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
def _carregar_cache_pr_di1(datas: list[dt.date]) -> pl.DataFrame:
|
|
16
|
+
"""Carrega e processa DI1 a partir do dataset PR para as datas informadas."""
|
|
17
|
+
return futuros_historico.carregar_pr(datas=datas, codigo_contrato="DI1")
|
|
18
|
+
|
|
19
|
+
|
|
14
20
|
def _carregar_com_intraday(datas: list[dt.date]) -> pl.DataFrame:
|
|
15
21
|
"""Busca dados de DI, incluindo dados intraday para datas ausentes no cache.
|
|
16
22
|
|
|
@@ -20,12 +26,15 @@ def _carregar_com_intraday(datas: list[dt.date]) -> pl.DataFrame:
|
|
|
20
26
|
Returns:
|
|
21
27
|
DataFrame com dados de DI para as datas solicitadas.
|
|
22
28
|
"""
|
|
23
|
-
# 1. Busca inicial no cache com as datas solicitadas pelo usuário.
|
|
24
|
-
df_cache =
|
|
29
|
+
# 1. Busca inicial no cache PR com as datas solicitadas pelo usuário.
|
|
30
|
+
df_cache = _carregar_cache_pr_di1(datas)
|
|
31
|
+
if df_cache.is_empty():
|
|
32
|
+
datas_cache = set()
|
|
33
|
+
else:
|
|
34
|
+
datas_cache = set(df_cache["TradeDate"].drop_nulls())
|
|
25
35
|
|
|
26
36
|
# 2. Identifica datas solicitadas que não estão no cache
|
|
27
37
|
datas_solicitadas = set(datas)
|
|
28
|
-
datas_cache = set(df_cache["TradeDate"].unique())
|
|
29
38
|
datas_faltantes = datas_solicitadas - datas_cache
|
|
30
39
|
|
|
31
40
|
# 3. Para cada data faltante, tenta buscar dados via API
|
|
@@ -48,7 +57,7 @@ def _carregar_com_intraday(datas: list[dt.date]) -> pl.DataFrame:
|
|
|
48
57
|
|
|
49
58
|
# 4. Retorna concatenação de todos os DataFrames disponíveis
|
|
50
59
|
if len(dfs_concat) == 0:
|
|
51
|
-
return pl.DataFrame()
|
|
60
|
+
return pl.DataFrame()
|
|
52
61
|
elif len(dfs_concat) == 1:
|
|
53
62
|
return dfs_concat[0]
|
|
54
63
|
else:
|
|
@@ -56,19 +65,19 @@ def _carregar_com_intraday(datas: list[dt.date]) -> pl.DataFrame:
|
|
|
56
65
|
|
|
57
66
|
|
|
58
67
|
def _obter_dados(datas: DateLike | ArrayLike) -> pl.DataFrame:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.with_columns(TradeDate=cv.converter_datas_expr("TradeDate"))
|
|
62
|
-
.drop_nulls(subset=["TradeDate"])
|
|
63
|
-
.select(pl.col("TradeDate").unique().sort())
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
if df_datas.is_empty():
|
|
68
|
+
datas_unicas = cv.converter_datas(datas)
|
|
69
|
+
if datas_unicas is None:
|
|
67
70
|
return pl.DataFrame()
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
if isinstance(datas_unicas, pl.Series):
|
|
73
|
+
datas_unicas = datas_unicas.drop_nulls().unique().sort().to_list()
|
|
74
|
+
else:
|
|
75
|
+
datas_unicas = [datas_unicas]
|
|
76
|
+
|
|
77
|
+
df = _carregar_com_intraday(datas_unicas)
|
|
70
78
|
|
|
71
|
-
df
|
|
79
|
+
if df.is_empty():
|
|
80
|
+
return pl.DataFrame()
|
|
72
81
|
|
|
73
82
|
return df.sort("TradeDate", "ExpirationDate")
|
|
74
83
|
|
|
@@ -102,31 +111,20 @@ def data(
|
|
|
102
111
|
Examples:
|
|
103
112
|
>>> from pyield import di1
|
|
104
113
|
>>> df = di1.data(dates="16-10-2024", month_start=True)
|
|
105
|
-
>>>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
╞════════════╪════════════════╪══════════════╪═══════════╪═══╪═════════╪═══════════╪════════════════╪═════════════╡
|
|
112
|
-
│ 2024-10-16 ┆ 2024-11-01 ┆ DI1X24 ┆ 16 ┆ … ┆ 0.10656 ┆ 0.10652 ┆ 0.10653 ┆ 0.10653 │
|
|
113
|
-
│ 2024-10-16 ┆ 2024-12-01 ┆ DI1Z24 ┆ 47 ┆ … ┆ 0.10914 ┆ 0.10914 ┆ 0.1091 ┆ 0.110726 │
|
|
114
|
-
│ 2024-10-16 ┆ 2025-01-01 ┆ DI1F25 ┆ 78 ┆ … ┆ 0.11174 ┆ 0.11164 ┆ 0.11164 ┆ 0.1154 │
|
|
115
|
-
│ 2024-10-16 ┆ 2025-02-01 ┆ DI1G25 ┆ 110 ┆ … ┆ 0.1137 ┆ 0.11365 ┆ 0.11362 ┆ 0.118314 │
|
|
116
|
-
│ 2024-10-16 ┆ 2025-03-01 ┆ DI1H25 ┆ 140 ┆ … ┆ 0.11595 ┆ 0.11565 ┆ 0.1157 ┆ 0.12343 │
|
|
117
|
-
│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
|
|
118
|
-
│ 2024-10-16 ┆ 2035-01-01 ┆ DI1F35 ┆ 3730 ┆ … ┆ 0.1267 ┆ 0.1264 ┆ 0.1265 ┆ 0.124455 │
|
|
119
|
-
│ 2024-10-16 ┆ 2036-01-01 ┆ DI1F36 ┆ 4095 ┆ … ┆ null ┆ null ┆ 0.1263 ┆ 0.124249 │
|
|
120
|
-
│ 2024-10-16 ┆ 2037-01-01 ┆ DI1F37 ┆ 4461 ┆ … ┆ null ┆ null ┆ 0.1263 ┆ 0.1263 │
|
|
121
|
-
│ 2024-10-16 ┆ 2038-01-01 ┆ DI1F38 ┆ 4828 ┆ … ┆ null ┆ null ┆ 0.1263 ┆ 0.1263 │
|
|
122
|
-
│ 2024-10-16 ┆ 2039-01-01 ┆ DI1F39 ┆ 5192 ┆ … ┆ null ┆ null ┆ 0.1263 ┆ 0.1263 │
|
|
123
|
-
└────────────┴────────────────┴──────────────┴───────────┴───┴─────────┴───────────┴────────────────┴─────────────┘
|
|
114
|
+
>>> {"TradeDate", "TickerSymbol", "ExpirationDate", "SettlementRate"}.issubset(
|
|
115
|
+
... set(df.columns)
|
|
116
|
+
... )
|
|
117
|
+
True
|
|
118
|
+
>>> df.shape[0] > 0
|
|
119
|
+
True
|
|
124
120
|
|
|
125
121
|
"""
|
|
126
122
|
if any_is_empty(dates):
|
|
127
123
|
return pl.DataFrame()
|
|
128
124
|
|
|
129
125
|
df = _obter_dados(datas=dates)
|
|
126
|
+
if df.is_empty():
|
|
127
|
+
return df
|
|
130
128
|
|
|
131
129
|
if pre_filter:
|
|
132
130
|
df_tpf = (
|
|
@@ -386,10 +384,10 @@ def interpolate_rate(
|
|
|
386
384
|
|
|
387
385
|
|
|
388
386
|
def available_trade_dates() -> pl.Series:
|
|
389
|
-
"""Retorna todas as datas de negociação disponíveis
|
|
387
|
+
"""Retorna todas as datas de negociação disponíveis para DI1 em futures.
|
|
390
388
|
|
|
391
|
-
Obtém valores distintos de 'TradeDate'
|
|
392
|
-
|
|
389
|
+
Obtém valores distintos de 'TradeDate' para DI1, com base no mesmo
|
|
390
|
+
dataset PR utilizado por `futures`.
|
|
393
391
|
|
|
394
392
|
Returns:
|
|
395
393
|
Series ordenada de datas de negociação únicas (dt.date) para as quais
|
|
@@ -397,23 +395,16 @@ def available_trade_dates() -> pl.Series:
|
|
|
397
395
|
|
|
398
396
|
Examples:
|
|
399
397
|
>>> from pyield import di1
|
|
400
|
-
>>> # Série
|
|
398
|
+
>>> # Série disponível no dataset PR começa em 2018-01-02
|
|
401
399
|
>>> di1.available_trade_dates().head(5)
|
|
402
400
|
shape: (5,)
|
|
403
401
|
Series: 'available_dates' [date]
|
|
404
402
|
[
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
403
|
+
2018-01-02
|
|
404
|
+
2018-01-03
|
|
405
|
+
2018-01-04
|
|
406
|
+
2018-01-05
|
|
407
|
+
2018-01-08
|
|
410
408
|
]
|
|
411
409
|
"""
|
|
412
|
-
|
|
413
|
-
obter_dataset_cacheado("di1")
|
|
414
|
-
.get_column("TradeDate")
|
|
415
|
-
.unique()
|
|
416
|
-
.sort()
|
|
417
|
-
.alias("available_dates")
|
|
418
|
-
)
|
|
419
|
-
return datas_disponiveis
|
|
410
|
+
return futuros_historico.listar_datas_disponiveis("DI1").alias("available_dates")
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections.abc import Sequence
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import polars as pl
|
|
6
|
+
|
|
7
|
+
import pyield._internal.converters as cv
|
|
8
|
+
from pyield import clock
|
|
9
|
+
from pyield._internal.types import DateLike, any_is_empty
|
|
10
|
+
from pyield.b3._contracts import normalizar_codigos_contrato
|
|
11
|
+
from pyield.b3._validar_pregao import data_negociacao_valida
|
|
12
|
+
from pyield.b3.futures import historical, intraday
|
|
13
|
+
|
|
14
|
+
OpcoesContrato = Literal[
|
|
15
|
+
"DI1",
|
|
16
|
+
"DDI",
|
|
17
|
+
"FRC",
|
|
18
|
+
"FRO",
|
|
19
|
+
"DAP",
|
|
20
|
+
"DOL",
|
|
21
|
+
"WDO",
|
|
22
|
+
"IND",
|
|
23
|
+
"WIN",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _buscar_intraday_ou_historico(
|
|
30
|
+
data_negociacao,
|
|
31
|
+
codigos_contrato: list[str],
|
|
32
|
+
full_report: bool,
|
|
33
|
+
) -> pl.DataFrame:
|
|
34
|
+
horario_atual = clock.now().time()
|
|
35
|
+
if horario_atual < intraday.HORA_INICIO_INTRADAY:
|
|
36
|
+
return pl.DataFrame()
|
|
37
|
+
|
|
38
|
+
if horario_atual >= intraday.HORA_FIM_INTRADAY:
|
|
39
|
+
df_historico = historical.historical(
|
|
40
|
+
data_negociacao, codigos_contrato, full_report
|
|
41
|
+
)
|
|
42
|
+
if not df_historico.is_empty():
|
|
43
|
+
logger.info("Dados consolidados disponíveis. Usando histórico.")
|
|
44
|
+
return df_historico
|
|
45
|
+
|
|
46
|
+
dataframes_intraday = [intraday.intraday(codigo) for codigo in codigos_contrato]
|
|
47
|
+
dataframes_intraday = [df for df in dataframes_intraday if not df.is_empty()]
|
|
48
|
+
if not dataframes_intraday:
|
|
49
|
+
return pl.DataFrame()
|
|
50
|
+
if len(dataframes_intraday) == 1:
|
|
51
|
+
return dataframes_intraday[0]
|
|
52
|
+
return pl.concat(dataframes_intraday, how="diagonal_relaxed").sort("TickerSymbol")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def futures(
|
|
56
|
+
date: DateLike,
|
|
57
|
+
contract_code: OpcoesContrato | str | Sequence[OpcoesContrato | str] | pl.Series,
|
|
58
|
+
full_report: bool = False,
|
|
59
|
+
) -> pl.DataFrame:
|
|
60
|
+
"""Busca dados de um contrato futuro da B3 para a data de referência.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
date: Data de referência para consulta.
|
|
64
|
+
contract_code: Código do contrato futuro na B3 ou coleção de códigos.
|
|
65
|
+
full_report: Se False (padrão), usa o simplified price report (SPR),
|
|
66
|
+
arquivo leve (~2 KB) com apenas preços de ajuste. Se True, usa o
|
|
67
|
+
price report completo (PR, ~2 MB) com todos os dados de negociação.
|
|
68
|
+
Relevante apenas quando o dado não está no cache.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
DataFrame Polars com os dados do contrato informado.
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
>>> df = futures("31-05-2024", "DI1")
|
|
75
|
+
>>> {"TradeDate", "TickerSymbol", "ExpirationDate", "SettlementRate"}.issubset(
|
|
76
|
+
... set(df.columns)
|
|
77
|
+
... )
|
|
78
|
+
True
|
|
79
|
+
>>> df.shape[0] > 0
|
|
80
|
+
True
|
|
81
|
+
|
|
82
|
+
>>> df = futures("31-05-2024", "DAP")
|
|
83
|
+
>>> {"TradeDate", "TickerSymbol", "ExpirationDate", "SettlementRate"}.issubset(
|
|
84
|
+
... set(df.columns)
|
|
85
|
+
... )
|
|
86
|
+
True
|
|
87
|
+
>>> df.shape[0] > 0
|
|
88
|
+
True
|
|
89
|
+
|
|
90
|
+
Véspera de Natal e Ano Novo não têm pregão:
|
|
91
|
+
|
|
92
|
+
>>> futures("24-12-2024", "DI1").is_empty()
|
|
93
|
+
True
|
|
94
|
+
>>> futures("31-12-2024", "DI1").is_empty()
|
|
95
|
+
True
|
|
96
|
+
|
|
97
|
+
Data futura e fim de semana retornam DataFrame vazio:
|
|
98
|
+
|
|
99
|
+
>>> import datetime as dt
|
|
100
|
+
>>> amanha = dt.date.today() + dt.timedelta(days=1)
|
|
101
|
+
>>> futures(amanha, "DI1").is_empty()
|
|
102
|
+
True
|
|
103
|
+
>>> futures("04-01-2025", "DI1").is_empty() # sábado
|
|
104
|
+
True
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
if any_is_empty(date, contract_code):
|
|
108
|
+
return pl.DataFrame()
|
|
109
|
+
|
|
110
|
+
codigos_contrato = normalizar_codigos_contrato(contract_code)
|
|
111
|
+
if not codigos_contrato:
|
|
112
|
+
return pl.DataFrame()
|
|
113
|
+
|
|
114
|
+
data_negociacao = cv.converter_datas(date)
|
|
115
|
+
if not data_negociacao_valida(data_negociacao):
|
|
116
|
+
return pl.DataFrame()
|
|
117
|
+
|
|
118
|
+
if intraday.data_intraday_valida(data_negociacao):
|
|
119
|
+
return _buscar_intraday_ou_historico(
|
|
120
|
+
data_negociacao=data_negociacao,
|
|
121
|
+
codigos_contrato=codigos_contrato,
|
|
122
|
+
full_report=full_report,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return historical.historical(data_negociacao, codigos_contrato, full_report)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
__all__ = ["futures"]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import polars as pl
|
|
2
|
+
|
|
3
|
+
from pyield import bday
|
|
4
|
+
|
|
5
|
+
_MAPA_MESES: dict[str, int] = {
|
|
6
|
+
"F": 1,
|
|
7
|
+
"G": 2,
|
|
8
|
+
"H": 3,
|
|
9
|
+
"J": 4,
|
|
10
|
+
"K": 5,
|
|
11
|
+
"M": 6,
|
|
12
|
+
"N": 7,
|
|
13
|
+
"Q": 8,
|
|
14
|
+
"U": 9,
|
|
15
|
+
"V": 10,
|
|
16
|
+
"X": 11,
|
|
17
|
+
"Z": 12,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def expr_dv01(
|
|
22
|
+
coluna_dias_uteis: str,
|
|
23
|
+
coluna_taxa: str,
|
|
24
|
+
coluna_preco: str,
|
|
25
|
+
) -> pl.Expr:
|
|
26
|
+
"""Retorna a expressão Polars para cálculo de DV01.
|
|
27
|
+
|
|
28
|
+
Fórmula:
|
|
29
|
+
DV01 = (Duration / (1 + taxa)) * preço * 0,0001
|
|
30
|
+
|
|
31
|
+
Onde:
|
|
32
|
+
- Duration = dias_uteis / 252
|
|
33
|
+
- taxa deve estar em formato decimal (ex.: 0.145)
|
|
34
|
+
- preço é o PU do contrato.
|
|
35
|
+
"""
|
|
36
|
+
duracao = pl.col(coluna_dias_uteis) / 252
|
|
37
|
+
duracao_modificada = duracao / (1 + pl.col(coluna_taxa))
|
|
38
|
+
return 0.0001 * duracao_modificada * pl.col(coluna_preco)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def adicionar_vencimento(
|
|
42
|
+
df: pl.DataFrame, codigo_contrato: str, coluna_ticker: str
|
|
43
|
+
) -> pl.DataFrame:
|
|
44
|
+
"""
|
|
45
|
+
Recebe um DataFrame Polars e ADICIONA a coluna 'ExpirationDate'.
|
|
46
|
+
|
|
47
|
+
- Pega a coluna 'coluna_ticker'.
|
|
48
|
+
- Extrai o código de vencimento.
|
|
49
|
+
- Converte para a data "bruta", sem ajuste de feriado.
|
|
50
|
+
- Garante que a data de vencimento é um dia útil.
|
|
51
|
+
- Retorna o DataFrame com a nova coluna ExpirationDate.
|
|
52
|
+
|
|
53
|
+
Assume tickers no formato padrão de futuros da B3 (ex.: DI1F25).
|
|
54
|
+
"""
|
|
55
|
+
dia_vencimento = 15 if "DAP" in codigo_contrato else 1
|
|
56
|
+
df = df.with_columns(
|
|
57
|
+
pl.date(
|
|
58
|
+
# Ano: posição 4-5 (2 dígitos) -> Int -> Soma 2000
|
|
59
|
+
# Funciona para tickers padrão de futuros (ex.: DI1F25)
|
|
60
|
+
year=pl.col(coluna_ticker).str.slice(4, 2).cast(pl.Int32, strict=False)
|
|
61
|
+
+ 2000,
|
|
62
|
+
# Mês: posição 3 (1 char = código do mês) -> mapeia para Int
|
|
63
|
+
month=pl.col(coluna_ticker)
|
|
64
|
+
.str.slice(3, 1)
|
|
65
|
+
.replace_strict(_MAPA_MESES, default=None, return_dtype=pl.Int8),
|
|
66
|
+
day=dia_vencimento,
|
|
67
|
+
).alias("ExpirationDate")
|
|
68
|
+
)
|
|
69
|
+
# Garante que a data de vencimento é um dia útil
|
|
70
|
+
df = df.with_columns(ExpirationDate=bday.offset_expr("ExpirationDate", 0))
|
|
71
|
+
|
|
72
|
+
return df
|