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.
Files changed (48) hide show
  1. {pyield-0.29.2 → pyield-0.29.4}/PKG-INFO +1 -1
  2. pyield-0.29.4/pyield/__about__.py +1 -0
  3. {pyield-0.29.2 → pyield-0.29.4}/pyield/__init__.py +4 -3
  4. {pyield-0.29.2 → pyield-0.29.4}/pyield/anbima/tpf.py +49 -77
  5. pyield-0.29.4/pyield/b3/__init__.py +9 -0
  6. {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3}/di.py +1 -2
  7. pyield-0.29.4/pyield/b3/di_over.py +70 -0
  8. {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3/futures}/__init__.py +2 -2
  9. {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3/futures}/historical.py +1 -1
  10. {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3/futures}/xml.py +1 -1
  11. {pyield-0.29.2 → pyield-0.29.4}/pyield/config.py +3 -0
  12. {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/ltn.py +1 -1
  13. {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/ntnf.py +1 -1
  14. {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/tools.py +1 -1
  15. pyield-0.29.2/pyield/__about__.py +0 -1
  16. {pyield-0.29.2 → pyield-0.29.4}/.gitignore +0 -0
  17. {pyield-0.29.2 → pyield-0.29.4}/LICENSE +0 -0
  18. {pyield-0.29.2 → pyield-0.29.4}/README.md +0 -0
  19. {pyield-0.29.2 → pyield-0.29.4}/pyield/anbima/__init__.py +0 -0
  20. {pyield-0.29.2 → pyield-0.29.4}/pyield/anbima/ima.py +0 -0
  21. {pyield-0.29.2 → pyield-0.29.4}/pyield/anbima/imaq.py +0 -0
  22. {pyield-0.29.2 → pyield-0.29.4}/pyield/anbima/ipca.py +0 -0
  23. {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3/futures}/common.py +0 -0
  24. {pyield-0.29.2/pyield/b3_futures → pyield-0.29.4/pyield/b3/futures}/intraday.py +0 -0
  25. {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/__init__.py +0 -0
  26. {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/auction.py +0 -0
  27. {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/bcdata.py +0 -0
  28. {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/ptax.py +0 -0
  29. {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/repos.py +0 -0
  30. {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/sec.py +0 -0
  31. {pyield-0.29.2 → pyield-0.29.4}/pyield/bc/vna.py +0 -0
  32. {pyield-0.29.2 → pyield-0.29.4}/pyield/bday.py +0 -0
  33. {pyield-0.29.2 → pyield-0.29.4}/pyield/conftest.py +0 -0
  34. {pyield-0.29.2 → pyield-0.29.4}/pyield/data_cache.py +0 -0
  35. {pyield-0.29.2 → pyield-0.29.4}/pyield/date_converter.py +0 -0
  36. {pyield-0.29.2 → pyield-0.29.4}/pyield/forwards.py +0 -0
  37. {pyield-0.29.2 → pyield-0.29.4}/pyield/holidays/__init__.py +0 -0
  38. {pyield-0.29.2 → pyield-0.29.4}/pyield/holidays/br_holidays_new.txt +0 -0
  39. {pyield-0.29.2 → pyield-0.29.4}/pyield/holidays/br_holidays_old.txt +0 -0
  40. {pyield-0.29.2 → pyield-0.29.4}/pyield/holidays/brholidays.py +0 -0
  41. {pyield-0.29.2 → pyield-0.29.4}/pyield/ibge/__init__.py +0 -0
  42. {pyield-0.29.2 → pyield-0.29.4}/pyield/ibge/ipca.py +0 -0
  43. {pyield-0.29.2 → pyield-0.29.4}/pyield/interpolator.py +0 -0
  44. {pyield-0.29.2 → pyield-0.29.4}/pyield/py.typed +0 -0
  45. {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/__init__.py +0 -0
  46. {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/lft.py +0 -0
  47. {pyield-0.29.2 → pyield-0.29.4}/pyield/tpf/ntnb.py +0 -0
  48. {pyield-0.29.2 → pyield-0.29.4}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PYield
3
- Version: 0.29.2
3
+ Version: 0.29.4
4
4
  Summary: A Python library for analysis of fixed income instruments in Brazil
5
5
  Project-URL: Source, https://github.com/crdcj/PYield
6
6
  Author-email: Carlos Carvalho <cr.cj@outlook.com>
@@ -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.b3_futures import futures
6
- from pyield.b3_futures.di import DIFutures
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 io
2
- import os
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 date_converter as dc
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
- # Before 13/05/2014 the file was zipped and the endpoint ended with ".exe"
19
- FORMAT_CHANGE_DATE = pd.to_datetime("13-05-2014", dayfirst=True)
14
+ logger = logging.getLogger(__name__)
20
15
 
21
16
 
22
- def _get_file_content(date: pd.Timestamp) -> str:
17
+ def _build_file_url(date: pd.Timestamp) -> str:
23
18
  url_date = date.strftime("%y%m%d")
24
- if date < FORMAT_CHANGE_DATE:
25
- filename = f"ms{url_date}.exe"
26
- else:
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
- # Tries to access the member URL first
30
- try:
31
- anbima_base_url = os.getenv("ANBIMA_BASE_URL")
32
- if anbima_base_url:
33
- anbima_member_url = f"{anbima_base_url}merc_sec/arqs"
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="latin-1",
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
- df[rate_cols] = (df[rate_cols] / 100).round(6)
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
- # Normalize the reference date
127
- date = dc.convert_input_dates(date)
128
- file_content = _get_file_content(date)
129
-
130
- if not file_content:
131
- return pd.DataFrame()
132
-
133
- df = _read_raw_df(file_content)
134
-
135
- df = _process_raw_df(df)
136
-
137
- if bond_type is None:
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
- else:
140
- df.query("BondType == @bond_type", inplace=True)
141
- return df.reset_index(drop=True)
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
- date = dc.convert_input_dates(date)
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", inplace=True)
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"], ignore_index=True)
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:
@@ -0,0 +1,9 @@
1
+ from pyield.b3.di import DIFutures
2
+ from pyield.b3.di_over import di_over
3
+ from pyield.b3.futures import futures
4
+
5
+ __all__ = [
6
+ "di_over",
7
+ "futures",
8
+ "DIFutures",
9
+ ]
@@ -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.b3_futures.historical import fetch_historical_df
9
- from pyield.b3_futures.intraday import fetch_intraday_df
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")
@@ -4,7 +4,7 @@ import pandas as pd
4
4
  import requests
5
5
 
6
6
  from pyield import bday
7
- from pyield.b3_futures import common
7
+ from pyield.b3.futures import common
8
8
 
9
9
  COUNT_CONVENTIONS = {
10
10
  "DAP": 252,
@@ -8,7 +8,7 @@ import requests
8
8
  from lxml import etree
9
9
 
10
10
  from pyield import bday
11
- from pyield.b3_futures import common
11
+ from pyield.b3.futures import common
12
12
 
13
13
 
14
14
  def _get_file_from_path(file_path: Path) -> io.BytesIO:
@@ -6,6 +6,9 @@ import tenacity
6
6
 
7
7
  logger = logging.getLogger(__name__)
8
8
 
9
+ # Default timeout values for HTTP requests (connect_timeout, read_timeout)
10
+ DEFAULT_TIMEOUT = (3.05, 30)
11
+
9
12
 
10
13
  def retry_if_not_404(exception):
11
14
  """
@@ -3,7 +3,7 @@ import pandas as pd
3
3
  from pyield import bday
4
4
  from pyield import date_converter as dc
5
5
  from pyield.anbima import tpf
6
- from pyield.b3_futures import di
6
+ from pyield.b3 import di
7
7
  from pyield.date_converter import DateScalar
8
8
  from pyield.tpf import tools as tt
9
9
 
@@ -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.b3_futures import di
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
 
@@ -5,7 +5,7 @@ import pandas as pd
5
5
 
6
6
  from pyield import date_converter as dc
7
7
  from pyield.anbima import tpf
8
- from pyield.b3_futures.di import DIFutures
8
+ from pyield.b3.di import DIFutures
9
9
  from pyield.date_converter import DateScalar
10
10
 
11
11
 
@@ -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