PYield 0.29.2__tar.gz → 0.29.4__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.29.2 → pyield-0.29.4}/PKG-INFO +1 -1
- pyield-0.29.4/pyield/__about__.py +1 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/__init__.py +4 -3
- {pyield-0.29.2 → pyield-0.29.4}/pyield/anbima/tpf.py +49 -77
- pyield-0.29.4/pyield/b3/__init__.py +9 -0
- {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3}/di.py +1 -2
- pyield-0.29.4/pyield/b3/di_over.py +70 -0
- {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3/futures}/__init__.py +2 -2
- {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3/futures}/historical.py +1 -1
- {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3/futures}/xml.py +1 -1
- {pyield-0.29.2 → pyield-0.29.4}/pyield/config.py +3 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/ltn.py +1 -1
- {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/ntnf.py +1 -1
- {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/tools.py +1 -1
- pyield-0.29.2/pyield/__about__.py +0 -1
- {pyield-0.29.2 → pyield-0.29.4}/.gitignore +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/LICENSE +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/README.md +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/anbima/__init__.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/anbima/ima.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/anbima/imaq.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/anbima/ipca.py +0 -0
- {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3/futures}/common.py +0 -0
- {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3/futures}/intraday.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/__init__.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/auction.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/bcdata.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/ptax.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/repos.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/sec.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/vna.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/bday.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/conftest.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/data_cache.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/date_converter.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/forwards.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/holidays/__init__.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/holidays/brholidays.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/ibge/__init__.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/ibge/ipca.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/interpolator.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/py.typed +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/__init__.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/lft.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/ntnb.py +0 -0
- {pyield-0.29.2 → pyield-0.29.4}/pyproject.toml +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.29.4"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from pyield import anbima, bc, bday, ibge
|
|
3
|
+
from pyield import anbima, b3, bc, bday, ibge
|
|
4
4
|
from pyield.__about__ import __version__
|
|
5
|
-
from pyield.
|
|
6
|
-
from pyield.
|
|
5
|
+
from pyield.b3 import futures
|
|
6
|
+
from pyield.b3.di import DIFutures
|
|
7
7
|
from pyield.forwards import forward_rates
|
|
8
8
|
from pyield.interpolator import Interpolator
|
|
9
9
|
from pyield.tpf import lft, ltn, ntnb, ntnf
|
|
@@ -22,6 +22,7 @@ __all__ = [
|
|
|
22
22
|
"ntnb",
|
|
23
23
|
"ntnf",
|
|
24
24
|
"bc",
|
|
25
|
+
"b3",
|
|
25
26
|
]
|
|
26
27
|
|
|
27
28
|
# Configura o logger do pacote principal com um NullHandler
|
|
@@ -1,77 +1,39 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import zipfile as zp
|
|
1
|
+
import logging
|
|
2
|
+
from urllib.error import HTTPError
|
|
4
3
|
|
|
5
4
|
import pandas as pd
|
|
6
|
-
import requests
|
|
7
5
|
|
|
8
6
|
from pyield import bday
|
|
9
|
-
from pyield import
|
|
7
|
+
from pyield.config import default_retry
|
|
10
8
|
from pyield.data_cache import get_anbima_dataset
|
|
11
|
-
from pyield.date_converter import DateScalar
|
|
9
|
+
from pyield.date_converter import DateScalar, convert_input_dates
|
|
12
10
|
|
|
13
|
-
# URL Constants
|
|
14
11
|
ANBIMA_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs"
|
|
15
|
-
ANBIMA_MEMBER_URL = "http://www.anbima.associados.rtm/merc_sec/arqs"
|
|
16
12
|
# URL example: https://www.anbima.com.br/informacoes/merc-sec/arqs/ms240614.txt
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
FORMAT_CHANGE_DATE = pd.to_datetime("13-05-2014", dayfirst=True)
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
20
15
|
|
|
21
16
|
|
|
22
|
-
def
|
|
17
|
+
def _build_file_url(date: pd.Timestamp) -> str:
|
|
23
18
|
url_date = date.strftime("%y%m%d")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
filename = f"ms{url_date}.txt"
|
|
19
|
+
filename = f"ms{url_date}.txt"
|
|
20
|
+
file_url = f"{ANBIMA_URL}/{filename}"
|
|
21
|
+
return file_url
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
anbima_headers = {"private-token": os.getenv("ANBIMA_TOKEN")}
|
|
35
|
-
else:
|
|
36
|
-
anbima_member_url = ANBIMA_MEMBER_URL
|
|
37
|
-
anbima_headers = None
|
|
38
|
-
|
|
39
|
-
file_url = f"{anbima_member_url}/{filename}"
|
|
40
|
-
r = requests.get(file_url, headers=anbima_headers, timeout=5)
|
|
41
|
-
# Checks if the response was successful (status code 200)
|
|
42
|
-
r.raise_for_status()
|
|
43
|
-
|
|
44
|
-
if date < FORMAT_CHANGE_DATE:
|
|
45
|
-
zip_file = zp.ZipFile(io.BytesIO(r.content))
|
|
46
|
-
file_content = zip_file.read(zip_file.namelist()[0]).decode("latin-1")
|
|
47
|
-
else:
|
|
48
|
-
file_content = r.text
|
|
49
|
-
|
|
50
|
-
except requests.exceptions.RequestException:
|
|
51
|
-
# If the member URL fails, tries to access the non-member URL
|
|
52
|
-
try:
|
|
53
|
-
file_url = f"{ANBIMA_URL}/{filename}"
|
|
54
|
-
r = requests.get(file_url, timeout=5)
|
|
55
|
-
r.raise_for_status() # Checks if the second attempt was successful
|
|
56
|
-
file_content = r.text
|
|
57
|
-
except requests.exceptions.RequestException:
|
|
58
|
-
# Both URLs failed
|
|
59
|
-
file_content = ""
|
|
60
|
-
|
|
61
|
-
return file_content
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _read_raw_df(file_content: str) -> pd.DataFrame:
|
|
65
|
-
return pd.read_csv(
|
|
66
|
-
io.StringIO(file_content),
|
|
23
|
+
|
|
24
|
+
@default_retry
|
|
25
|
+
def _read_raw_df(file_url: str) -> pd.DataFrame:
|
|
26
|
+
df = pd.read_csv(
|
|
27
|
+
file_url,
|
|
67
28
|
sep="@",
|
|
68
|
-
encoding="
|
|
29
|
+
encoding="latin1",
|
|
69
30
|
skiprows=2,
|
|
70
31
|
decimal=",",
|
|
71
32
|
thousands=".",
|
|
72
33
|
na_values=["--"],
|
|
73
34
|
dtype_backend="numpy_nullable",
|
|
74
35
|
)
|
|
36
|
+
return df
|
|
75
37
|
|
|
76
38
|
|
|
77
39
|
def _process_raw_df(df_raw: pd.DataFrame) -> pd.DataFrame:
|
|
@@ -97,7 +59,8 @@ def _process_raw_df(df_raw: pd.DataFrame) -> pd.DataFrame:
|
|
|
97
59
|
|
|
98
60
|
# Remove percentage from rates
|
|
99
61
|
rate_cols = [col for col in df.columns if "Rate" in col]
|
|
100
|
-
|
|
62
|
+
# Data has 6 decimal places, so we round to 7 decimal places
|
|
63
|
+
df[rate_cols] = (df[rate_cols] / 100).round(7)
|
|
101
64
|
|
|
102
65
|
df["ReferenceDate"] = pd.to_datetime(df["ReferenceDate"], format="%Y%m%d")
|
|
103
66
|
df["MaturityDate"] = pd.to_datetime(df["MaturityDate"], format="%Y%m%d")
|
|
@@ -107,7 +70,7 @@ def _process_raw_df(df_raw: pd.DataFrame) -> pd.DataFrame:
|
|
|
107
70
|
|
|
108
71
|
|
|
109
72
|
def tpf_data(date: DateScalar, bond_type: str | None = None) -> pd.DataFrame:
|
|
110
|
-
"""Fetch and process TPF market data from ANBIMA.
|
|
73
|
+
"""Fetch and process TPF secondary market data from ANBIMA.
|
|
111
74
|
|
|
112
75
|
This function retrieves bond market data from the ANBIMA website for a
|
|
113
76
|
specified reference date. It handles different file formats based on the date
|
|
@@ -121,24 +84,34 @@ def tpf_data(date: DateScalar, bond_type: str | None = None) -> pd.DataFrame:
|
|
|
121
84
|
Returns:
|
|
122
85
|
pd.DataFrame: A DataFrame containing bond market data.
|
|
123
86
|
Returns an empty DataFrame if data is not available for the
|
|
124
|
-
specified date.
|
|
87
|
+
specified date (weekends, holidays, or unavailable data).
|
|
125
88
|
"""
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
89
|
+
try:
|
|
90
|
+
date = convert_input_dates(date)
|
|
91
|
+
date_log = date.strftime("%d/%m/%Y")
|
|
92
|
+
file_url = _build_file_url(date)
|
|
93
|
+
df = _read_raw_df(file_url)
|
|
94
|
+
if df.empty:
|
|
95
|
+
logger.info(
|
|
96
|
+
f"No TPF secondary market data from ANBIMA for {date_log}. "
|
|
97
|
+
"Returning empty DataFrame."
|
|
98
|
+
)
|
|
99
|
+
return df
|
|
100
|
+
df = _process_raw_df(df)
|
|
101
|
+
if bond_type:
|
|
102
|
+
df = df.query("BondType == @bond_type").reset_index(drop=True)
|
|
138
103
|
return df
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
104
|
+
except HTTPError as e:
|
|
105
|
+
if e.code == 404: # noqa
|
|
106
|
+
logger.info(
|
|
107
|
+
f"No TPF secondary market data from ANBIMA for {date_log}. "
|
|
108
|
+
"Returning empty DataFrame."
|
|
109
|
+
)
|
|
110
|
+
return pd.DataFrame()
|
|
111
|
+
raise # Propagate other HTTP errors
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.error(f"Error fetching TPF data for {date_log}: {e}")
|
|
114
|
+
raise
|
|
142
115
|
|
|
143
116
|
|
|
144
117
|
def tpf_rates(
|
|
@@ -166,9 +139,8 @@ def tpf_rates(
|
|
|
166
139
|
available for the specified date.
|
|
167
140
|
"""
|
|
168
141
|
df = get_anbima_dataset()
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
df.query("ReferenceDate == @date", inplace=True)
|
|
142
|
+
date = convert_input_dates(date)
|
|
143
|
+
df = df.query("ReferenceDate == @date").reset_index(drop=True)
|
|
172
144
|
|
|
173
145
|
if df.empty:
|
|
174
146
|
# Try to fetch the data from the Anbima website
|
|
@@ -179,13 +151,13 @@ def tpf_rates(
|
|
|
179
151
|
return pd.DataFrame()
|
|
180
152
|
|
|
181
153
|
if bond_type:
|
|
182
|
-
df.query("BondType == @bond_type"
|
|
154
|
+
df = df.query("BondType == @bond_type").reset_index(drop=True)
|
|
183
155
|
|
|
184
156
|
if adj_maturities:
|
|
185
157
|
df["MaturityDate"] = bday.offset(df["MaturityDate"], 0)
|
|
186
158
|
|
|
187
159
|
df = df[["BondType", "MaturityDate", "IndicativeRate"]].copy()
|
|
188
|
-
return df.sort_values(["BondType", "MaturityDate"]
|
|
160
|
+
return df.sort_values(["BondType", "MaturityDate"]).reset_index(drop=True)
|
|
189
161
|
|
|
190
162
|
|
|
191
163
|
def tpf_pre_maturities(date: DateScalar) -> pd.Series:
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
|
|
3
|
-
import pyield.b3_futures as b3
|
|
4
3
|
import pyield.date_converter as dc
|
|
5
4
|
import pyield.forwards as tl
|
|
6
|
-
from pyield import bday, interpolator
|
|
5
|
+
from pyield import b3, bday, interpolator
|
|
7
6
|
from pyield.data_cache import get_anbima_dataset, get_di_dataset
|
|
8
7
|
from pyield.date_converter import DateArray, DateScalar
|
|
9
8
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import ftplib
|
|
2
|
+
|
|
3
|
+
from pyield.date_converter import DateScalar, convert_input_dates
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def di_over(date: DateScalar) -> float:
|
|
7
|
+
"""
|
|
8
|
+
Gets the DI (Interbank Deposit) rate for a specific date from B3/CETIP FTP server.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
date (str): Date in DD/MM/YYYY format
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
float: DI rate for the specified date
|
|
15
|
+
|
|
16
|
+
Raises:
|
|
17
|
+
ValueError: If date is not in the correct format
|
|
18
|
+
ftplib.error_perm: If the file is not found or there is a permission error
|
|
19
|
+
Exception: For any other unexpected error
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
>>> di_over("28/02/2025")
|
|
23
|
+
0.1315
|
|
24
|
+
"""
|
|
25
|
+
ftp = None
|
|
26
|
+
try:
|
|
27
|
+
# Convert date to file format
|
|
28
|
+
date_obj = convert_input_dates(date)
|
|
29
|
+
filename = date_obj.strftime("%Y%m%d.txt")
|
|
30
|
+
|
|
31
|
+
# Connect to FTP and get the file
|
|
32
|
+
ftp = ftplib.FTP("ftp.cetip.com.br")
|
|
33
|
+
ftp.login()
|
|
34
|
+
ftp.cwd("/MediaCDI")
|
|
35
|
+
|
|
36
|
+
# Read rate
|
|
37
|
+
lines = []
|
|
38
|
+
ftp.retrlines(f"RETR {filename}", lines.append)
|
|
39
|
+
|
|
40
|
+
# Close FTP connection
|
|
41
|
+
ftp.quit()
|
|
42
|
+
|
|
43
|
+
# Get and format rate
|
|
44
|
+
if lines:
|
|
45
|
+
raw_rate = lines[0].strip()
|
|
46
|
+
rate = int(raw_rate) / 10000
|
|
47
|
+
return round(rate, 5)
|
|
48
|
+
else:
|
|
49
|
+
return "Empty file or no data"
|
|
50
|
+
|
|
51
|
+
except ValueError as e:
|
|
52
|
+
return f"Date format error: {e}"
|
|
53
|
+
except ftplib.error_perm as e:
|
|
54
|
+
return f"File access error: {e}"
|
|
55
|
+
except Exception as e:
|
|
56
|
+
return f"Unexpected error: {e}"
|
|
57
|
+
finally:
|
|
58
|
+
# Ensure FTP connection is closed
|
|
59
|
+
if ftp:
|
|
60
|
+
try:
|
|
61
|
+
ftp.quit()
|
|
62
|
+
except: # noqa
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Example usage
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
date = "28/02/2025"
|
|
69
|
+
rate = di_over(date)
|
|
70
|
+
print(f"DI Rate for {date}: {rate}")
|
|
@@ -5,8 +5,8 @@ from zoneinfo import ZoneInfo
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
|
|
7
7
|
from pyield import date_converter as dc
|
|
8
|
-
from pyield.
|
|
9
|
-
from pyield.
|
|
8
|
+
from pyield.b3.futures.historical import fetch_historical_df
|
|
9
|
+
from pyield.b3.futures.intraday import fetch_intraday_df
|
|
10
10
|
from pyield.date_converter import DateScalar
|
|
11
11
|
|
|
12
12
|
TIMEZONE_BZ = ZoneInfo("America/Sao_Paulo")
|
|
@@ -7,7 +7,7 @@ from pyield import bday
|
|
|
7
7
|
from pyield import date_converter as dc
|
|
8
8
|
from pyield import interpolator as ip
|
|
9
9
|
from pyield.anbima import tpf
|
|
10
|
-
from pyield.
|
|
10
|
+
from pyield.b3 import di
|
|
11
11
|
from pyield.date_converter import DateScalar
|
|
12
12
|
from pyield.tpf import tools as tt
|
|
13
13
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.29.2"
|
|
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
|
|
File without changes
|