PYield 0.30.0__tar.gz → 0.30.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.
- {pyield-0.30.0 → pyield-0.30.2}/PKG-INFO +2 -2
- pyield-0.30.2/pyield/__about__.py +1 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/anbima/__init__.py +2 -0
- pyield-0.30.2/pyield/anbima/ettj.py +135 -0
- pyield-0.30.2/pyield/anbima/ipca.py +85 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/anbima/tpf.py +19 -8
- {pyield-0.30.0 → pyield-0.30.2}/pyield/b3/di_over.py +12 -12
- {pyield-0.30.0 → pyield-0.30.2}/pyield/b3/futures/intraday.py +3 -3
- {pyield-0.30.0 → pyield-0.30.2}/pyield/bc/auction.py +1 -1
- {pyield-0.30.0 → pyield-0.30.2}/pyield/bc/bcdata.py +1 -1
- {pyield-0.30.0 → pyield-0.30.2}/pyield/bc/repo.py +1 -1
- {pyield-0.30.0 → pyield-0.30.2}/pyield/bc/sec.py +1 -1
- {pyield-0.30.0 → pyield-0.30.2}/pyield/bc/vna.py +1 -1
- {pyield-0.30.0 → pyield-0.30.2}/pyield/ibge/ipca.py +1 -1
- {pyield-0.30.0 → pyield-0.30.2}/pyield/tpf/lft.py +2 -2
- {pyield-0.30.0 → pyield-0.30.2}/pyield/tpf/ltn.py +3 -3
- {pyield-0.30.0 → pyield-0.30.2}/pyield/tpf/ntnf.py +2 -2
- {pyield-0.30.0 → pyield-0.30.2}/pyproject.toml +1 -1
- pyield-0.30.0/pyield/__about__.py +0 -1
- pyield-0.30.0/pyield/anbima/ipca.py +0 -52
- {pyield-0.30.0 → pyield-0.30.2}/.gitignore +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/LICENSE +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/README.md +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/__init__.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/anbima/ima.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/anbima/imaq.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/b3/__init__.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/b3/di.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/b3/futures/__init__.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/b3/futures/common.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/b3/futures/historical.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/b3/futures/xml.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/bc/__init__.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/bc/ptax.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/bday.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/conftest.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/data_cache.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/date_converter.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/forwards.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/holidays/__init__.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/holidays/brholidays.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/ibge/__init__.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/interpolator.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/py.typed +0 -0
- /pyield-0.30.0/pyield/config.py → /pyield-0.30.2/pyield/retry.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/tpf/__init__.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/tpf/ntnb.py +0 -0
- {pyield-0.30.0 → pyield-0.30.2}/pyield/tpf/tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PYield
|
|
3
|
-
Version: 0.30.
|
|
3
|
+
Version: 0.30.2
|
|
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>
|
|
@@ -34,7 +34,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
34
34
|
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
35
35
|
Requires-Python: >=3.11
|
|
36
36
|
Requires-Dist: numpy>=2.0.0
|
|
37
|
-
Requires-Dist: pandas[
|
|
37
|
+
Requires-Dist: pandas[html,parquet]>=2.0.0
|
|
38
38
|
Requires-Dist: requests>=2.31.0
|
|
39
39
|
Requires-Dist: tenacity>=9.0.0
|
|
40
40
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.30.2"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from pyield.anbima.ettj import last_ettj
|
|
1
2
|
from pyield.anbima.ima import last_ima
|
|
2
3
|
from pyield.anbima.imaq import imaq
|
|
3
4
|
from pyield.anbima.ipca import ipca_projection
|
|
@@ -10,4 +11,5 @@ __all__ = [
|
|
|
10
11
|
"tpf_data",
|
|
11
12
|
"tpf_rates",
|
|
12
13
|
"tpf_pre_maturities",
|
|
14
|
+
"last_ettj",
|
|
13
15
|
]
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from io import StringIO
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from pyield.forwards import forward_rates
|
|
8
|
+
from pyield.retry import default_retry
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
LAST_ETTJ_URL = "https://www.anbima.com.br/informacoes/est-termo/CZ-down.asp"
|
|
12
|
+
|
|
13
|
+
# Anbima ETTJ data has 4 decimal places in percentage values
|
|
14
|
+
# We will round to 6 decimal places to avoid floating point errors
|
|
15
|
+
ROUND_DIGITS = 6
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@default_retry
|
|
19
|
+
def _get_last_content() -> str:
|
|
20
|
+
"""Fetches the raw yield curve data from ANBIMA."""
|
|
21
|
+
request_payload = {
|
|
22
|
+
"Idioma": "PT",
|
|
23
|
+
"Dt_Ref": "",
|
|
24
|
+
"saida": "csv",
|
|
25
|
+
}
|
|
26
|
+
r = requests.post(LAST_ETTJ_URL, data=request_payload)
|
|
27
|
+
r.raise_for_status()
|
|
28
|
+
return r.text
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _convert_text_to_df(text: str) -> pd.DataFrame:
|
|
32
|
+
"""Converts the raw yield curve text to a DataFrame."""
|
|
33
|
+
raw_df = pd.read_csv(
|
|
34
|
+
StringIO(text),
|
|
35
|
+
sep=";",
|
|
36
|
+
encoding="latin1",
|
|
37
|
+
skiprows=5,
|
|
38
|
+
dtype_backend="numpy_nullable",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
idx = raw_df.query("Vertices =='PREFIXADOS (CIRCULAR 3.361)'").first_valid_index() # noqa
|
|
42
|
+
raw_df.query("index < @idx", inplace=True)
|
|
43
|
+
csv_buffer = StringIO()
|
|
44
|
+
|
|
45
|
+
# Save the DataFrame as CSV in memory buffer to be read again
|
|
46
|
+
# It's easier than handling values with commas and dots directly in the DataFrame
|
|
47
|
+
raw_df.to_csv(csv_buffer, index=False)
|
|
48
|
+
csv_buffer.seek(0) # Move the buffer cursor to the beginning
|
|
49
|
+
|
|
50
|
+
df = pd.read_csv(
|
|
51
|
+
csv_buffer,
|
|
52
|
+
decimal=",",
|
|
53
|
+
thousands=".",
|
|
54
|
+
dtype_backend="numpy_nullable",
|
|
55
|
+
)
|
|
56
|
+
file_date_str = text[0:10] # 09/09/2024
|
|
57
|
+
file_date = pd.to_datetime(file_date_str, format="%d/%m/%Y")
|
|
58
|
+
df["date"] = file_date
|
|
59
|
+
|
|
60
|
+
return df
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _process_df(df: pd.DataFrame) -> pd.DataFrame:
|
|
64
|
+
"""Processes the raw yield curve DataFrame to calculate rates and forward rates."""
|
|
65
|
+
# Rename columns
|
|
66
|
+
rename_dict = {
|
|
67
|
+
"Vertices": "vertex",
|
|
68
|
+
"ETTJ IPCA": "real_rate",
|
|
69
|
+
"ETTJ PREF": "nominal_rate",
|
|
70
|
+
"Inflação Implícita": "implied_inflation",
|
|
71
|
+
}
|
|
72
|
+
df.rename(columns=rename_dict, inplace=True)
|
|
73
|
+
|
|
74
|
+
# Divide float columns by 100 and round to 6 decimal places
|
|
75
|
+
df["real_rate"] = (df["real_rate"] / 100).round(ROUND_DIGITS)
|
|
76
|
+
df["nominal_rate"] = (df["nominal_rate"] / 100).round(ROUND_DIGITS)
|
|
77
|
+
df["implied_inflation"] = (df["implied_inflation"] / 100).round(ROUND_DIGITS)
|
|
78
|
+
|
|
79
|
+
df["real_rate_fwd"] = forward_rates(
|
|
80
|
+
business_days=df["vertex"],
|
|
81
|
+
zero_rates=df["real_rate"],
|
|
82
|
+
groupby_dates=df["date"],
|
|
83
|
+
)
|
|
84
|
+
df["nominal_rate_fwd"] = forward_rates(
|
|
85
|
+
business_days=df["vertex"],
|
|
86
|
+
zero_rates=df["nominal_rate"],
|
|
87
|
+
groupby_dates=df["date"],
|
|
88
|
+
)
|
|
89
|
+
df["implied_inflation_fwd"] = forward_rates(
|
|
90
|
+
business_days=df["vertex"],
|
|
91
|
+
zero_rates=df["implied_inflation"],
|
|
92
|
+
groupby_dates=df["date"],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
column_order = [
|
|
96
|
+
"date",
|
|
97
|
+
"vertex",
|
|
98
|
+
"nominal_rate",
|
|
99
|
+
"real_rate",
|
|
100
|
+
"implied_inflation",
|
|
101
|
+
"nominal_rate_fwd",
|
|
102
|
+
"real_rate_fwd",
|
|
103
|
+
"implied_inflation_fwd",
|
|
104
|
+
]
|
|
105
|
+
df = df[column_order].copy()
|
|
106
|
+
|
|
107
|
+
return df
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def last_ettj() -> pd.DataFrame:
|
|
111
|
+
"""
|
|
112
|
+
Retrieves and processes the latest Brazilian yield curve data from ANBIMA.
|
|
113
|
+
|
|
114
|
+
This function fetches the most recent yield curve data published by ANBIMA,
|
|
115
|
+
containing real rates (IPCA-indexed), nominal rates, and implied inflation
|
|
116
|
+
at various vertices (time points). Both spot rates and forward rates are
|
|
117
|
+
calculated and included in the returned DataFrame.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
pd.DataFrame: A DataFrame containing the following columns:
|
|
121
|
+
- date: Reference date of the yield curve
|
|
122
|
+
- vertex: Time point in business days
|
|
123
|
+
- nominal_rate: Zero-coupon nominal interest rate
|
|
124
|
+
- real_rate: Zero-coupon real interest rate (IPCA-indexed)
|
|
125
|
+
- implied_inflation: Implied inflation rate (break-even inflation)
|
|
126
|
+
- nominal_rate_fwd: Forward nominal interest rate
|
|
127
|
+
- real_rate_fwd: Forward real interest rate
|
|
128
|
+
- implied_inflation_fwd: Forward implied inflation rate
|
|
129
|
+
|
|
130
|
+
Note:
|
|
131
|
+
All rates are expressed in decimal format (e.g., 0.12 for 12%).
|
|
132
|
+
"""
|
|
133
|
+
text = _get_last_content()
|
|
134
|
+
df = _convert_text_to_df(text)
|
|
135
|
+
return _process_df(df)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import locale
|
|
3
|
+
import warnings
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import requests
|
|
8
|
+
from bs4 import XMLParsedAsHTMLWarning
|
|
9
|
+
|
|
10
|
+
warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class IndicatorProjection:
|
|
15
|
+
reference_period: pd.Period # Reference month as a pd.Period object
|
|
16
|
+
projected_value: float # Projected value
|
|
17
|
+
last_updated: pd.Timestamp # Date and time of the last update
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def ipca_projection() -> IndicatorProjection:
|
|
21
|
+
"""
|
|
22
|
+
Retrieves the current IPCA projection from the ANBIMA website.
|
|
23
|
+
|
|
24
|
+
This function makes an HTTP request to the ANBIMA website, extracts HTML tables
|
|
25
|
+
containing economic indicators, and specifically processes the IPCA projection data.
|
|
26
|
+
|
|
27
|
+
Process:
|
|
28
|
+
1. Accesses the ANBIMA indicators webpage
|
|
29
|
+
2. Extracts the third table that contains the IPCA projection
|
|
30
|
+
3. Locates the row labeled as "IPCA1"
|
|
31
|
+
4. Extracts the projection value and converts it to decimal format
|
|
32
|
+
5. Extracts and formats the reference month of the projection
|
|
33
|
+
6. Extracts the date and time of the last update
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
IndicatorProjection: An object containing:
|
|
37
|
+
- reference_period (pd.Period): Reference period of the projection
|
|
38
|
+
- projected_value (float): Projected IPCA value as a decimal number
|
|
39
|
+
- last_updated (pd.Timestamp): Date and time of the last data update
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
requests.RequestException: If there are connection issues with the ANBIMA site
|
|
43
|
+
ValueError: If the expected data is not found in the page structure
|
|
44
|
+
locale.Error: If the 'pt_BR.UTF-8' locale is not available on the system
|
|
45
|
+
|
|
46
|
+
Notes:
|
|
47
|
+
- The function requires internet connection to access the ANBIMA website
|
|
48
|
+
- The structure of the ANBIMA page may change, which could affect the function
|
|
49
|
+
- Temporarily changes the locale to pt_BR.UTF-8 to process dates in Portuguese
|
|
50
|
+
"""
|
|
51
|
+
url = "https://www.anbima.com.br/informacoes/indicadores/"
|
|
52
|
+
r = requests.get(url)
|
|
53
|
+
r.encoding = "latin1"
|
|
54
|
+
dfs = pd.read_html(
|
|
55
|
+
io.StringIO(r.text),
|
|
56
|
+
flavor="html5lib",
|
|
57
|
+
decimal=",",
|
|
58
|
+
thousands=".",
|
|
59
|
+
dtype_backend="numpy_nullable",
|
|
60
|
+
)
|
|
61
|
+
# The IPCA projection is in the third table
|
|
62
|
+
df = dfs[2]
|
|
63
|
+
|
|
64
|
+
last_update_str = df.iat[0, 0].split("Atualização:")[-1].strip()
|
|
65
|
+
last_update = pd.to_datetime(last_update_str, format="%d/%m/%Y - %H:%M h")
|
|
66
|
+
|
|
67
|
+
ipca_row = df.loc[df[0] == "IPCA1"]
|
|
68
|
+
ipca_value = ipca_row.iloc[0, 2]
|
|
69
|
+
ipca_value = pd.to_numeric(ipca_value, errors="coerce")
|
|
70
|
+
ipca_value = round(float(ipca_value) / 100, 4)
|
|
71
|
+
|
|
72
|
+
# Extract and format the reference month
|
|
73
|
+
ipca_date = ipca_row.iloc[0, 1]
|
|
74
|
+
ipca_date = str(ipca_date)
|
|
75
|
+
ipca_date = ipca_date.split("(")[-1].split(")")[0]
|
|
76
|
+
locale.setlocale(locale.LC_TIME, "pt_BR.UTF-8")
|
|
77
|
+
ipca_date = pd.to_datetime(ipca_date, format="%b/%y")
|
|
78
|
+
reference_period = ipca_date.to_period("M")
|
|
79
|
+
locale.setlocale(locale.LC_TIME, "") # Reset locale to default
|
|
80
|
+
|
|
81
|
+
return IndicatorProjection(
|
|
82
|
+
last_updated=last_update,
|
|
83
|
+
reference_period=reference_period,
|
|
84
|
+
projected_value=ipca_value,
|
|
85
|
+
)
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import Literal
|
|
2
3
|
from urllib.error import HTTPError
|
|
3
4
|
|
|
4
5
|
import pandas as pd
|
|
5
6
|
|
|
6
7
|
from pyield import bday
|
|
7
|
-
from pyield.config import default_retry
|
|
8
8
|
from pyield.data_cache import get_anbima_dataset
|
|
9
9
|
from pyield.date_converter import DateScalar, convert_input_dates
|
|
10
|
+
from pyield.retry import default_retry
|
|
10
11
|
|
|
12
|
+
BOND_TYPES = Literal["LTN", "NTN-B", "NTN-C", "NTN-F", "LFT"]
|
|
11
13
|
ANBIMA_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs"
|
|
12
14
|
# URL example: https://www.anbima.com.br/informacoes/merc-sec/arqs/ms240614.txt
|
|
13
15
|
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
15
17
|
|
|
16
18
|
|
|
19
|
+
def _bond_type_mapping(bond_type: str) -> str:
|
|
20
|
+
bond_type = bond_type.upper()
|
|
21
|
+
bond_type_mapping = {"NTNB": "NTN-B", "NTNC": "NTN-C", "NTNF": "NTN-F"}
|
|
22
|
+
return bond_type_mapping.get(bond_type, bond_type)
|
|
23
|
+
|
|
24
|
+
|
|
17
25
|
def _build_file_url(date: pd.Timestamp) -> str:
|
|
18
26
|
url_date = date.strftime("%y%m%d")
|
|
19
27
|
filename = f"ms{url_date}.txt"
|
|
@@ -59,8 +67,9 @@ def _process_raw_df(df_raw: pd.DataFrame) -> pd.DataFrame:
|
|
|
59
67
|
|
|
60
68
|
# Remove percentage from rates
|
|
61
69
|
rate_cols = [col for col in df.columns if "Rate" in col]
|
|
62
|
-
#
|
|
63
|
-
|
|
70
|
+
# Rate columns have percentage values with 4 decimal places in percentage values
|
|
71
|
+
# We will round to 6 decimal places to avoid floating point errors
|
|
72
|
+
df[rate_cols] = (df[rate_cols] / 100).round(6)
|
|
64
73
|
|
|
65
74
|
df["ReferenceDate"] = pd.to_datetime(df["ReferenceDate"], format="%Y%m%d")
|
|
66
75
|
df["MaturityDate"] = pd.to_datetime(df["MaturityDate"], format="%Y%m%d")
|
|
@@ -86,9 +95,10 @@ def tpf_data(date: DateScalar, bond_type: str | None = None) -> pd.DataFrame:
|
|
|
86
95
|
Returns an empty DataFrame if data is not available for the
|
|
87
96
|
specified date (weekends, holidays, or unavailable data).
|
|
88
97
|
"""
|
|
98
|
+
date = convert_input_dates(date)
|
|
99
|
+
date_log = date.strftime("%d/%m/%Y")
|
|
100
|
+
|
|
89
101
|
try:
|
|
90
|
-
date = convert_input_dates(date)
|
|
91
|
-
date_log = date.strftime("%d/%m/%Y")
|
|
92
102
|
file_url = _build_file_url(date)
|
|
93
103
|
df = _read_raw_df(file_url)
|
|
94
104
|
if df.empty:
|
|
@@ -99,8 +109,9 @@ def tpf_data(date: DateScalar, bond_type: str | None = None) -> pd.DataFrame:
|
|
|
99
109
|
return df
|
|
100
110
|
df = _process_raw_df(df)
|
|
101
111
|
if bond_type:
|
|
112
|
+
bond_type = _bond_type_mapping(bond_type)
|
|
102
113
|
df = df.query("BondType == @bond_type").reset_index(drop=True)
|
|
103
|
-
return df
|
|
114
|
+
return df.sort_values(["BondType", "MaturityDate"]).reset_index(drop=True)
|
|
104
115
|
except HTTPError as e:
|
|
105
116
|
if e.code == 404: # noqa
|
|
106
117
|
logger.info(
|
|
@@ -109,8 +120,8 @@ def tpf_data(date: DateScalar, bond_type: str | None = None) -> pd.DataFrame:
|
|
|
109
120
|
)
|
|
110
121
|
return pd.DataFrame()
|
|
111
122
|
raise # Propagate other HTTP errors
|
|
112
|
-
except Exception
|
|
113
|
-
logger.
|
|
123
|
+
except Exception:
|
|
124
|
+
logger.exception(f"Error fetching TPF data for {date_log}")
|
|
114
125
|
raise
|
|
115
126
|
|
|
116
127
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import ftplib
|
|
2
|
+
import logging
|
|
2
3
|
|
|
3
4
|
from pyield.date_converter import DateScalar, convert_input_dates
|
|
4
5
|
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
5
8
|
|
|
6
9
|
def di_over(date: DateScalar) -> float:
|
|
7
10
|
"""
|
|
@@ -46,14 +49,18 @@ def di_over(date: DateScalar) -> float:
|
|
|
46
49
|
rate = int(raw_rate) / 10000
|
|
47
50
|
return round(rate, 5)
|
|
48
51
|
else:
|
|
49
|
-
|
|
52
|
+
logger.error(f"No data found for date {date}")
|
|
53
|
+
return float("nan")
|
|
50
54
|
|
|
51
55
|
except ValueError as e:
|
|
52
|
-
|
|
56
|
+
logger.error(f"Date format error: {e}")
|
|
57
|
+
raise
|
|
53
58
|
except ftplib.error_perm as e:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
logger.error(f"File access error: {e}")
|
|
60
|
+
raise
|
|
61
|
+
except Exception:
|
|
62
|
+
logger.exception("Unexpected error")
|
|
63
|
+
raise
|
|
57
64
|
finally:
|
|
58
65
|
# Ensure FTP connection is closed
|
|
59
66
|
if ftp:
|
|
@@ -61,10 +68,3 @@ def di_over(date: DateScalar) -> float:
|
|
|
61
68
|
ftp.quit()
|
|
62
69
|
except: # noqa
|
|
63
70
|
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}")
|
|
@@ -9,7 +9,7 @@ from pyield import bday
|
|
|
9
9
|
TIMEZONE_BZ = ZoneInfo("America/Sao_Paulo")
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def _fetch_b3_df(
|
|
12
|
+
def _fetch_b3_df(contract_code: str) -> pd.DataFrame:
|
|
13
13
|
"""
|
|
14
14
|
Fetch the latest data for a given future code from B3 derivatives quotation API.
|
|
15
15
|
|
|
@@ -24,13 +24,13 @@ def _fetch_b3_df(future_code: str) -> pd.DataFrame:
|
|
|
24
24
|
Exception: An exception is raised if the data fetch operation fails.
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
-
url = f"https://cotacao.b3.com.br/mds/api/v1/DerivativeQuotation/{
|
|
27
|
+
url = f"https://cotacao.b3.com.br/mds/api/v1/DerivativeQuotation/{contract_code}"
|
|
28
28
|
|
|
29
29
|
try:
|
|
30
30
|
r = requests.get(url, timeout=10)
|
|
31
31
|
r.raise_for_status() # Check for HTTP request errors
|
|
32
32
|
except requests.exceptions.RequestException:
|
|
33
|
-
raise Exception(f"Failed to fetch data for {
|
|
33
|
+
raise Exception(f"Failed to fetch data for {contract_code}.") from None
|
|
34
34
|
|
|
35
35
|
r.encoding = "utf-8" # Explicitly set response encoding to utf-8 for consistency
|
|
36
36
|
|
|
@@ -14,8 +14,8 @@ import requests
|
|
|
14
14
|
|
|
15
15
|
from pyield import bday
|
|
16
16
|
from pyield import date_converter as dc
|
|
17
|
-
from pyield.config import default_retry
|
|
18
17
|
from pyield.date_converter import DateScalar
|
|
18
|
+
from pyield.retry import default_retry
|
|
19
19
|
from pyield.tpf.ntnb import duration as duration_b
|
|
20
20
|
from pyield.tpf.ntnf import duration as duration_f
|
|
21
21
|
|
|
@@ -20,8 +20,8 @@ from urllib.error import HTTPError
|
|
|
20
20
|
|
|
21
21
|
import pandas as pd
|
|
22
22
|
|
|
23
|
-
from pyield.config import default_retry
|
|
24
23
|
from pyield.date_converter import DateScalar, convert_input_dates
|
|
24
|
+
from pyield.retry import default_retry
|
|
25
25
|
|
|
26
26
|
logger = logging.getLogger(__name__)
|
|
27
27
|
|
|
@@ -13,8 +13,8 @@ import requests
|
|
|
13
13
|
|
|
14
14
|
from pyield import bday
|
|
15
15
|
from pyield import date_converter as dc
|
|
16
|
-
from pyield.config import default_retry
|
|
17
16
|
from pyield.date_converter import DateScalar
|
|
17
|
+
from pyield.retry import default_retry
|
|
18
18
|
|
|
19
19
|
"""Dicionário com o mapeamento das colunas da API do BC para o DataFrame final
|
|
20
20
|
Chaves com comentário serão descartadas ao final do processamento
|
|
@@ -3,8 +3,8 @@ import logging
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import requests
|
|
5
5
|
|
|
6
|
-
from pyield.config import default_retry
|
|
7
6
|
from pyield.date_converter import DateScalar, convert_input_dates
|
|
7
|
+
from pyield.retry import default_retry
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger(__name__)
|
|
10
10
|
IPCA_URL = "https://servicodados.ibge.gov.br/api/v3/agregados/6691/periodos/"
|
|
@@ -94,9 +94,9 @@ def premium(lft_rate: float, selic_over: float) -> float:
|
|
|
94
94
|
>>> lft_rate = 0.1695 / 100 # 0.1695%
|
|
95
95
|
>>> selic_over = 10.40 / 100 # 10.40%
|
|
96
96
|
>>> yd.lft.premium(lft_rate, selic_over)
|
|
97
|
-
1.
|
|
97
|
+
1.017120519283759
|
|
98
98
|
"""
|
|
99
99
|
adjusted_lft_rate = (lft_rate + 1) * (selic_over + 1) - 1
|
|
100
100
|
f1 = (adjusted_lft_rate + 1) ** (1 / 252) - 1
|
|
101
101
|
f2 = (selic_over + 1) ** (1 / 252) - 1
|
|
102
|
-
return
|
|
102
|
+
return f1 / f2
|
|
@@ -175,14 +175,14 @@ def premium(ltn_rate: float, di_rate: float) -> float:
|
|
|
175
175
|
LTN rate for 01-01-2030: 0.118746
|
|
176
176
|
DI (JAN30) Settlement rate: 0.11725
|
|
177
177
|
>>> yd.ltn.premium(0.118746, 0.11725)
|
|
178
|
-
1.
|
|
178
|
+
1.0120718007994287
|
|
179
179
|
"""
|
|
180
180
|
# Cálculo das taxas diárias
|
|
181
181
|
ltn_factor = (1 + ltn_rate) ** (1 / 252)
|
|
182
182
|
di_factor = (1 + di_rate) ** (1 / 252)
|
|
183
183
|
|
|
184
184
|
# Retorno do cálculo do prêmio
|
|
185
|
-
return
|
|
185
|
+
return (ltn_factor - 1) / (di_factor - 1)
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
def historical_premium(
|
|
@@ -202,7 +202,7 @@ def historical_premium(
|
|
|
202
202
|
|
|
203
203
|
Examples:
|
|
204
204
|
>>> yd.ltn.historical_premium("22-08-2024", "01-01-2030")
|
|
205
|
-
1.
|
|
205
|
+
1.0120718007994287
|
|
206
206
|
|
|
207
207
|
"""
|
|
208
208
|
# Convert input dates to a consistent format
|
|
@@ -532,7 +532,7 @@ def premium(
|
|
|
532
532
|
... di_expirations=exp_dates,
|
|
533
533
|
... di_rates=di_rates,
|
|
534
534
|
... )
|
|
535
|
-
1.
|
|
535
|
+
1.0099602136954626
|
|
536
536
|
|
|
537
537
|
Notes:
|
|
538
538
|
- The function adjusts coupon payment dates to business days and calculates
|
|
@@ -572,7 +572,7 @@ def premium(
|
|
|
572
572
|
factor_ntnf = (1 + ntnf_rate) ** (1 / 252)
|
|
573
573
|
factor_di = (1 + di_ytm) ** (1 / 252)
|
|
574
574
|
premium_np = (factor_ntnf - 1) / (factor_di - 1)
|
|
575
|
-
return
|
|
575
|
+
return float(premium_np)
|
|
576
576
|
|
|
577
577
|
|
|
578
578
|
def historical_premium(
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.30.0"
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import locale
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
|
|
4
|
-
import pandas as pd
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@dataclass
|
|
8
|
-
class IndicatorProjection:
|
|
9
|
-
reference_period: pd.Period # Reference month as a pd.Period object
|
|
10
|
-
projected_value: float # Projected value
|
|
11
|
-
last_updated: pd.Timestamp # Date and time of the last update
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def ipca_projection() -> IndicatorProjection:
|
|
15
|
-
"""
|
|
16
|
-
This function retrieves and parses the Excel file that contains economic indicators,
|
|
17
|
-
specifically looking for the IPCA projection. It extracts the date of the last
|
|
18
|
-
update and the IPCA projection for the reference month.
|
|
19
|
-
|
|
20
|
-
Data file format example after parsing:
|
|
21
|
-
- ['Data e Hora da Última Atualização: 19/04/2024 - 18:55 h', '', '']
|
|
22
|
-
- ...
|
|
23
|
-
- ['IPCA1', 'Projeção (abr/24)', 0.35]
|
|
24
|
-
- ...
|
|
25
|
-
"""
|
|
26
|
-
# Define the URL and get the data
|
|
27
|
-
url = "https://www.anbima.com.br/informacoes/indicadores/arqs/indicadores.xls"
|
|
28
|
-
df = pd.read_excel(url, dtype_backend="numpy_nullable")
|
|
29
|
-
|
|
30
|
-
last_update_str = df.columns[0].split("Atualização:")[-1].strip()
|
|
31
|
-
last_update = pd.to_datetime(last_update_str, format="%d/%m/%Y - %H:%M h")
|
|
32
|
-
|
|
33
|
-
ipca_row = df[df.iloc[:, 0] == "IPCA1"]
|
|
34
|
-
|
|
35
|
-
ipca_value = ipca_row.iloc[0, 2]
|
|
36
|
-
ipca_value = pd.to_numeric(ipca_value, errors="coerce")
|
|
37
|
-
ipca_value = round(float(ipca_value) / 100, 4)
|
|
38
|
-
|
|
39
|
-
# Extract and format the reference month
|
|
40
|
-
ipca_date = ipca_row.iloc[0, 1]
|
|
41
|
-
ipca_date = str(ipca_date)
|
|
42
|
-
ipca_date = ipca_date.split("(")[-1].split(")")[0]
|
|
43
|
-
locale.setlocale(locale.LC_TIME, "pt_BR.UTF-8")
|
|
44
|
-
ipca_date = pd.to_datetime(ipca_date, format="%b/%y")
|
|
45
|
-
reference_period = ipca_date.to_period("M")
|
|
46
|
-
locale.setlocale(locale.LC_TIME, "") # Reset locale to default
|
|
47
|
-
|
|
48
|
-
return IndicatorProjection(
|
|
49
|
-
last_updated=last_update,
|
|
50
|
-
reference_period=reference_period,
|
|
51
|
-
projected_value=ipca_value,
|
|
52
|
-
)
|
|
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
|