PYield 0.22.2__tar.gz → 0.23.1__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.22.2 → pyield-0.23.1}/PKG-INFO +1 -1
- pyield-0.23.1/pyield/__about__.py +1 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/bonds/bond_tools.py +1 -1
- {pyield-0.22.2 → pyield-0.23.1}/pyield/bonds/ntnb.py +76 -3
- {pyield-0.22.2 → pyield-0.23.1}/pyield/di.py +36 -19
- {pyield-0.22.2 → pyield-0.23.1}/pyield/indicators.py +68 -34
- {pyield-0.22.2 → pyield-0.23.1}/pyproject.toml +1 -1
- pyield-0.22.2/pyield/__about__.py +0 -1
- {pyield-0.22.2 → pyield-0.23.1}/LICENSE +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/README.md +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/__init__.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/anbima.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/b3_futures/__init__.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/b3_futures/common.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/b3_futures/historical.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/b3_futures/intraday.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/b3_futures/xml.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/bday.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/bonds/__init__.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/bonds/lft.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/bonds/ltn.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/bonds/ntnf.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/conftest.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/data_cache.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/date_converter.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/holidays/__init__.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/holidays/brholidays.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/interpolator.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/projections.py +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/py.typed +0 -0
- {pyield-0.22.2 → pyield-0.23.1}/pyield/tools.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.23.1"
|
|
@@ -79,7 +79,7 @@ def di_spreads(reference_date: str | pd.Timestamp) -> pd.DataFrame:
|
|
|
79
79
|
# Fetch DI rates for the reference date
|
|
80
80
|
reference_date = dc.convert_input_dates(reference_date)
|
|
81
81
|
di = DIFutures(reference_date, adj_expirations=True)
|
|
82
|
-
df_di = di.data
|
|
82
|
+
df_di = di.data
|
|
83
83
|
if "SettlementRate" not in df_di.columns:
|
|
84
84
|
raise ValueError("DI rates data is missing the 'SettlementRate' column.")
|
|
85
85
|
|
|
@@ -60,6 +60,29 @@ def rates(reference_date: str | pd.Timestamp) -> pd.DataFrame:
|
|
|
60
60
|
|
|
61
61
|
Returns:
|
|
62
62
|
pd.DataFrame: DataFrame with columns "MaturityDate" and "IndicativeRate".
|
|
63
|
+
|
|
64
|
+
Returned columns:
|
|
65
|
+
- MaturityDate: The maturity date of the bond.
|
|
66
|
+
- IndicativeRate: The indicative rate for the bond.
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
>>> yd.ntnb.rates("16-08-2024")
|
|
70
|
+
MaturityDate IndicativeRate
|
|
71
|
+
0 2025-05-15 0.063893
|
|
72
|
+
1 2026-08-15 0.066095
|
|
73
|
+
2 2027-05-15 0.064164
|
|
74
|
+
3 2028-08-15 0.063199
|
|
75
|
+
4 2029-05-15 0.061753
|
|
76
|
+
5 2030-08-15 0.059997
|
|
77
|
+
6 2032-08-15 0.060025
|
|
78
|
+
7 2033-05-15 0.059876
|
|
79
|
+
8 2035-05-15 0.059557
|
|
80
|
+
9 2040-08-15 0.058873
|
|
81
|
+
10 2045-05-15 0.060013
|
|
82
|
+
11 2050-08-15 0.060247
|
|
83
|
+
12 2055-05-15 0.059926
|
|
84
|
+
13 2060-08-15 0.060179
|
|
85
|
+
|
|
63
86
|
"""
|
|
64
87
|
ntnb_rates = anbima.rates(reference_date, "NTN-B")
|
|
65
88
|
if ntnb_rates.empty:
|
|
@@ -76,9 +99,31 @@ def maturities(reference_date: str | pd.Timestamp) -> pd.Series:
|
|
|
76
99
|
|
|
77
100
|
Returns:
|
|
78
101
|
pd.Series: Series containing the maturity dates for the NTN-B bonds.
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
|
|
105
|
+
>>> yd.ntnb.maturities("16-08-2024")
|
|
106
|
+
0 2025-05-15
|
|
107
|
+
1 2026-08-15
|
|
108
|
+
2 2027-05-15
|
|
109
|
+
3 2028-08-15
|
|
110
|
+
4 2029-05-15
|
|
111
|
+
5 2030-08-15
|
|
112
|
+
6 2032-08-15
|
|
113
|
+
7 2033-05-15
|
|
114
|
+
8 2035-05-15
|
|
115
|
+
9 2040-08-15
|
|
116
|
+
10 2045-05-15
|
|
117
|
+
11 2050-08-15
|
|
118
|
+
12 2055-05-15
|
|
119
|
+
13 2060-08-15
|
|
120
|
+
dtype: datetime64[ns]
|
|
121
|
+
|
|
79
122
|
"""
|
|
80
123
|
df_rates = rates(reference_date)
|
|
81
|
-
|
|
124
|
+
s_maturities = df_rates["MaturityDate"]
|
|
125
|
+
s_maturities.name = None
|
|
126
|
+
return s_maturities
|
|
82
127
|
|
|
83
128
|
|
|
84
129
|
def _generate_coupon_dates_map(
|
|
@@ -236,6 +281,8 @@ def quotation(
|
|
|
236
281
|
99.3651
|
|
237
282
|
>>> yd.ntnb.quotation("31-05-2024", "15-08-2060", 0.061878)
|
|
238
283
|
99.5341
|
|
284
|
+
>>> yd.ntnb.quotation("15-08-2024", "15-08-2032", 0.05929)
|
|
285
|
+
100.6409
|
|
239
286
|
"""
|
|
240
287
|
# Validate and normalize dates
|
|
241
288
|
settlement = dc.convert_input_dates(settlement)
|
|
@@ -281,6 +328,8 @@ def price(
|
|
|
281
328
|
Examples:
|
|
282
329
|
>>> yd.ntnb.price(4299.160173, 99.3651)
|
|
283
330
|
4271.864805
|
|
331
|
+
>>> yd.ntnb.price(4315.498383, 100.6409)
|
|
332
|
+
4343.156412
|
|
284
333
|
"""
|
|
285
334
|
return bt.truncate(vna * quotation / 100, 6)
|
|
286
335
|
|
|
@@ -298,7 +347,6 @@ def spot_rates(
|
|
|
298
347
|
the spot rates that discount each bond's cash flows to its current
|
|
299
348
|
price.
|
|
300
349
|
|
|
301
|
-
|
|
302
350
|
Args:
|
|
303
351
|
settlement (str | pd.Timestamp): The reference date for settlement.
|
|
304
352
|
maturities (pd.Series): Series of maturity dates for the bonds.
|
|
@@ -307,6 +355,32 @@ def spot_rates(
|
|
|
307
355
|
Returns:
|
|
308
356
|
pd.DataFrame: DataFrame with columns "MaturityDate", "SpotRate".
|
|
309
357
|
|
|
358
|
+
Examples:
|
|
359
|
+
# Get the NTN-B rates for a specific reference date
|
|
360
|
+
>>> df_rates = yd.ntnb.rates("16-08-2024")
|
|
361
|
+
|
|
362
|
+
# Calculate the spot rates considering the settlement at the reference date
|
|
363
|
+
>>> yd.ntnb.spot_rates(
|
|
364
|
+
... settlement="16-08-2024",
|
|
365
|
+
... maturities=df_rates["MaturityDate"],
|
|
366
|
+
... rates=df_rates["IndicativeRate"],
|
|
367
|
+
... )
|
|
368
|
+
MaturityDate SpotRate
|
|
369
|
+
0 2025-05-15 0.063894
|
|
370
|
+
1 2026-08-15 0.066141
|
|
371
|
+
2 2027-05-15 0.064087
|
|
372
|
+
3 2028-08-15 0.063057
|
|
373
|
+
4 2029-05-15 0.061458
|
|
374
|
+
5 2030-08-15 0.059491
|
|
375
|
+
6 2032-08-15 0.059652
|
|
376
|
+
7 2033-05-15 0.059497
|
|
377
|
+
8 2035-05-15 0.059151
|
|
378
|
+
9 2040-08-15 0.058326
|
|
379
|
+
10 2045-05-15 0.060371
|
|
380
|
+
11 2050-08-15 0.060772
|
|
381
|
+
12 2055-05-15 0.059909
|
|
382
|
+
13 2060-08-15 0.060652
|
|
383
|
+
|
|
310
384
|
Notes:
|
|
311
385
|
The calculation of the spot rates for NTN-B bonds considers the following steps:
|
|
312
386
|
- Map all all possible payment dates up to the longest maturity date.
|
|
@@ -315,7 +389,6 @@ def spot_rates(
|
|
|
315
389
|
- Calculate the real spot rates for each maturity date.
|
|
316
390
|
- Columns in the returned DataFrame:
|
|
317
391
|
- MaturityDate: The maturity date of the bond.
|
|
318
|
-
- YTM: The yield to maturity rate for the bond.
|
|
319
392
|
- SpotRate: The real spot rate for the bond.
|
|
320
393
|
"""
|
|
321
394
|
# Process and validate the input data
|
|
@@ -30,19 +30,19 @@ class DIFutures:
|
|
|
30
30
|
Initialize the DIFutures instance with a specific trade date.
|
|
31
31
|
|
|
32
32
|
Args:
|
|
33
|
-
trade_date (str | pd.Timestamp): The
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
contracts to match LTN and NTN-F bond maturities.
|
|
33
|
+
trade_date (str | pd.Timestamp): The date to retrieve the contract data.
|
|
34
|
+
adj_expirations (bool): If True, adjusts the expiration dates to the start
|
|
35
|
+
of the month.
|
|
36
|
+
prefixed_filter (bool): If True, filters the DI contracts to match LTN and
|
|
37
|
+
NTN-F bond maturities.
|
|
39
38
|
"""
|
|
40
39
|
self._trade_date = dc.convert_input_dates(trade_date)
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
40
|
+
self._adj_expirations = adj_expirations
|
|
41
|
+
self._prefixed_filter = prefixed_filter
|
|
43
42
|
|
|
43
|
+
@property
|
|
44
44
|
def data(self) -> pd.DataFrame:
|
|
45
|
-
"""Retrieve DI contract
|
|
45
|
+
"""Retrieve DI contract DataFrame for the initialized trade date."""
|
|
46
46
|
df = (
|
|
47
47
|
get_di_dataset()
|
|
48
48
|
.query("TradeDate == @self.trade_date")
|
|
@@ -51,6 +51,7 @@ class DIFutures:
|
|
|
51
51
|
|
|
52
52
|
if df.empty:
|
|
53
53
|
df = futures(contract_code="DI1", trade_date=self._trade_date)
|
|
54
|
+
|
|
54
55
|
if df.empty:
|
|
55
56
|
return pd.DataFrame()
|
|
56
57
|
|
|
@@ -58,10 +59,18 @@ class DIFutures:
|
|
|
58
59
|
if "DaysToExpiration" in df.columns:
|
|
59
60
|
df.drop(columns=["DaysToExpiration"], inplace=True)
|
|
60
61
|
|
|
61
|
-
if self.
|
|
62
|
+
if self._prefixed_filter:
|
|
63
|
+
df_anbima = get_anbima_dataset()
|
|
64
|
+
|
|
65
|
+
# Find the closest Anbima reference date to the trade date
|
|
66
|
+
anbima_dates = (
|
|
67
|
+
df_anbima["ReferenceDate"].drop_duplicates().reset_index(drop=True)
|
|
68
|
+
)
|
|
69
|
+
dates_diff = (anbima_dates - self._trade_date).abs()
|
|
70
|
+
closest_date = anbima_dates[dates_diff.idxmin()] # noqa
|
|
71
|
+
|
|
62
72
|
df_pre = (
|
|
63
|
-
|
|
64
|
-
.query("ReferenceDate == @self.trade_date")
|
|
73
|
+
df_anbima.query("ReferenceDate == @closest_date")
|
|
65
74
|
.query("BondType in ['LTN', 'NTN-F']")
|
|
66
75
|
.drop_duplicates(ignore_index=True)
|
|
67
76
|
)
|
|
@@ -69,7 +78,7 @@ class DIFutures:
|
|
|
69
78
|
adj_pre_maturities = bday.offset(pre_maturities, 0) # noqa
|
|
70
79
|
df.query("ExpirationDate in @adj_pre_maturities", inplace=True)
|
|
71
80
|
|
|
72
|
-
if self.
|
|
81
|
+
if self._adj_expirations:
|
|
73
82
|
df["ExpirationDate"] = (
|
|
74
83
|
df["ExpirationDate"].dt.to_period("M").dt.to_timestamp()
|
|
75
84
|
)
|
|
@@ -87,7 +96,7 @@ class DIFutures:
|
|
|
87
96
|
Returns:
|
|
88
97
|
pd.Series: A Series of unique expiration dates for DI contracts.
|
|
89
98
|
"""
|
|
90
|
-
df = self.data
|
|
99
|
+
df = self.data.drop_duplicates(subset=["ExpirationDate"])
|
|
91
100
|
return df["ExpirationDate"].sort_values(ignore_index=True)
|
|
92
101
|
|
|
93
102
|
@property
|
|
@@ -107,7 +116,7 @@ class DIFutures:
|
|
|
107
116
|
- ForwardRate: The forward rate calculated between successive expiration
|
|
108
117
|
dates.
|
|
109
118
|
"""
|
|
110
|
-
df = self.data
|
|
119
|
+
df = self.data
|
|
111
120
|
if df.empty:
|
|
112
121
|
return pd.DataFrame()
|
|
113
122
|
|
|
@@ -125,21 +134,30 @@ class DIFutures:
|
|
|
125
134
|
extrapolate: bool = False,
|
|
126
135
|
) -> float:
|
|
127
136
|
"""Retrieve the DI rate for a specified expiration date."""
|
|
128
|
-
expiration =
|
|
137
|
+
expiration = dc.convert_input_dates(expiration)
|
|
138
|
+
|
|
139
|
+
if self._adj_expirations:
|
|
140
|
+
# Force the expiration date to be the start of the month
|
|
141
|
+
expiration = expiration.to_period("M").to_timestamp()
|
|
142
|
+
else:
|
|
143
|
+
# Force the expiration date to be a business day
|
|
144
|
+
expiration = bday.offset(expiration, 0)
|
|
129
145
|
|
|
130
146
|
if not interpolate and extrapolate:
|
|
131
147
|
raise ValueError("Extrapolation is not allowed without interpolation.")
|
|
132
148
|
|
|
133
|
-
df = self.data
|
|
149
|
+
df = self.data
|
|
150
|
+
|
|
134
151
|
if df.empty:
|
|
135
152
|
return float("NaN")
|
|
136
153
|
|
|
137
154
|
df_exp = df.query("ExpirationDate == @expiration")
|
|
155
|
+
|
|
138
156
|
if df_exp.empty and not interpolate:
|
|
139
157
|
return float("NaN")
|
|
140
158
|
|
|
141
159
|
if expiration in df_exp["ExpirationDate"].values:
|
|
142
|
-
return float(df_exp["SettlementRate"].
|
|
160
|
+
return float(df_exp["SettlementRate"].iat[0])
|
|
143
161
|
|
|
144
162
|
if not interpolate:
|
|
145
163
|
return float("NaN")
|
|
@@ -166,7 +184,6 @@ class DIFutures:
|
|
|
166
184
|
@trade_date.setter
|
|
167
185
|
def trade_date(self, value: str | pd.Timestamp):
|
|
168
186
|
self._trade_date = dc.convert_input_dates(value)
|
|
169
|
-
return self._trade_date
|
|
170
187
|
|
|
171
188
|
@property
|
|
172
189
|
def adj_expirations(self) -> bool:
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import time
|
|
2
3
|
from typing import Literal
|
|
3
4
|
|
|
4
5
|
import pandas as pd
|
|
5
6
|
import requests
|
|
7
|
+
from requests.exceptions import RequestException
|
|
6
8
|
|
|
7
9
|
from pyield import date_converter as dc
|
|
8
10
|
|
|
9
11
|
type IndicatorCode = Literal["IPCA_MR", "SELIC_TARGET", "SELIC_OVER", "DI", "VNA_LFT"]
|
|
10
|
-
|
|
12
|
+
|
|
13
|
+
TIMEOUT = (5, 20)
|
|
14
|
+
MAX_ATTEMPTS = 5
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
def indicator(
|
|
@@ -89,7 +93,7 @@ def ipca_monthly_rate(reference_date: pd.Timestamp) -> float:
|
|
|
89
93
|
api_url = f"https://servicodados.ibge.gov.br/api/v3/agregados/6691/periodos/{ipca_date}/variaveis/63?localidades=N1[all]"
|
|
90
94
|
|
|
91
95
|
# Send a GET request to the API
|
|
92
|
-
response = requests.get(api_url, timeout=
|
|
96
|
+
response = requests.get(api_url, timeout=TIMEOUT)
|
|
93
97
|
|
|
94
98
|
# Raises HTTPError, if one occurred
|
|
95
99
|
response.raise_for_status()
|
|
@@ -105,59 +109,89 @@ def ipca_monthly_rate(reference_date: pd.Timestamp) -> float:
|
|
|
105
109
|
|
|
106
110
|
|
|
107
111
|
def _selic_target(reference_date: pd.Timestamp) -> float:
|
|
112
|
+
"""
|
|
113
|
+
Fetches the SELIC Target rate for a specific reference date.
|
|
114
|
+
|
|
115
|
+
The SELIC Target rate is the official rate set by the Central Bank of Brazil.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
reference_date (pd.Timestamp): The date for which to fetch the SELIC Target.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
float: The SELIC Target rate as a float rounded to 4 decimal places or NaN if
|
|
122
|
+
the rate is not available.
|
|
123
|
+
"""
|
|
108
124
|
# https://api.bcb.gov.br/dados/serie/bcdata.sgs.432/dados?formato=json&dataInicial=12/04/2024&dataFinal=12/04/2024
|
|
109
125
|
selic_date = reference_date.strftime("%d/%m/%Y")
|
|
110
126
|
api_url = f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.432/dados?formato=json&dataInicial={selic_date}&dataFinal={selic_date}"
|
|
111
|
-
response = requests.get(api_url, timeout=10)
|
|
112
|
-
response.raise_for_status()
|
|
113
127
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
128
|
+
attempt = 0
|
|
129
|
+
while attempt < MAX_ATTEMPTS:
|
|
130
|
+
try:
|
|
131
|
+
response = requests.get(api_url, timeout=TIMEOUT)
|
|
132
|
+
response.raise_for_status() # Raise an error for bad HTTP response
|
|
133
|
+
|
|
134
|
+
# Try parsing JSON, raises an error if response is not valid JSON
|
|
135
|
+
data = response.json()
|
|
136
|
+
|
|
137
|
+
# Access the value directly, any issue will raise an exception
|
|
138
|
+
value = data[0]["valor"]
|
|
139
|
+
return round(float(value) / 100, 4)
|
|
140
|
+
|
|
141
|
+
except (RequestException, KeyError, IndexError, ValueError) as e:
|
|
142
|
+
msg = f"Error fetching SELIC Target rate (attempt {attempt + 1}): {e}"
|
|
143
|
+
logging.error(msg)
|
|
144
|
+
|
|
145
|
+
# Increment attempt count and add backoff delay
|
|
146
|
+
attempt += 1
|
|
147
|
+
time.sleep(1.5**attempt) # Exponential backoff: 1.5, 2.3, 3.9, 5.1, 7.6 seconds
|
|
148
|
+
|
|
149
|
+
# After all retries, return NaN
|
|
150
|
+
return float("nan")
|
|
118
151
|
|
|
119
152
|
|
|
120
153
|
def _selic_over(reference_date: pd.Timestamp) -> float:
|
|
121
154
|
"""
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
155
|
+
Fetches the SELIC Over rate for a specific reference date.
|
|
156
|
+
|
|
157
|
+
The SELIC Over rate is the daily average interest rate effectively practiced
|
|
158
|
+
between banks in the interbank market, using public securities as collateral.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
reference_date (pd.Timestamp): The date for which to fetch the SELIC Over rate.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
float: The SELIC Over rate as a float rounded to 4 decimal places or NaN if
|
|
165
|
+
the rate is not available.
|
|
126
166
|
"""
|
|
167
|
+
|
|
127
168
|
formatted_date = reference_date.strftime("%d/%m/%Y")
|
|
128
169
|
# https://api.bcb.gov.br/dados/serie/bcdata.sgs.1178/dados?formato=json&dataInicial=12/04/2024&dataFinal=12/04/2024
|
|
129
170
|
api_url = f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.1178/dados?formato=json&dataInicial={formatted_date}&dataFinal={formatted_date}"
|
|
130
171
|
|
|
131
|
-
|
|
132
|
-
retries = 3
|
|
133
|
-
timeout = 10
|
|
134
|
-
backoff_factor = 2
|
|
172
|
+
attempt = 0
|
|
135
173
|
|
|
136
|
-
|
|
174
|
+
while attempt < MAX_ATTEMPTS:
|
|
137
175
|
try:
|
|
138
|
-
response = requests.get(api_url, timeout=
|
|
139
|
-
response.raise_for_status() #
|
|
140
|
-
|
|
141
|
-
if formatted_date not in response.text:
|
|
142
|
-
return float("nan")
|
|
176
|
+
response = requests.get(api_url, timeout=TIMEOUT)
|
|
177
|
+
response.raise_for_status() # Handle 4xx/5xx HTTP errors
|
|
143
178
|
|
|
179
|
+
# Try parsing JSON, raises an error if response is not valid JSON
|
|
144
180
|
data = response.json()
|
|
145
181
|
|
|
146
|
-
|
|
147
|
-
return float("nan")
|
|
182
|
+
# Access the value directly, any issue will raise an exception
|
|
148
183
|
value = data[0]["valor"]
|
|
149
184
|
return round(float(value) / 100, 4)
|
|
150
185
|
|
|
151
|
-
except
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
print(f"Connection error on attempt {attempt + 1}: {e}. Retrying...")
|
|
155
|
-
except requests.exceptions.RequestException as e:
|
|
156
|
-
print(f"An error occurred: {e}")
|
|
157
|
-
return float("nan")
|
|
186
|
+
except (RequestException, KeyError, IndexError, ValueError) as e:
|
|
187
|
+
msg = f"Error fetching SELIC Over rate (attempt {attempt + 1}): {e}"
|
|
188
|
+
logging.error(msg)
|
|
158
189
|
|
|
159
|
-
|
|
190
|
+
# Increment attempt count and add backoff delay
|
|
191
|
+
attempt += 1
|
|
192
|
+
time.sleep(1.5**attempt) # Exponential backoff: 1.5, 2.3, 3.9, 5.1, 7.6 seconds
|
|
160
193
|
|
|
194
|
+
# After all retries, return NaN
|
|
161
195
|
return float("nan")
|
|
162
196
|
|
|
163
197
|
|
|
@@ -165,7 +199,7 @@ def _di(reference_date: pd.Timestamp) -> float:
|
|
|
165
199
|
# https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial=12/04/2024&dataFinal=12/04/2024
|
|
166
200
|
di_date = reference_date.strftime("%d/%m/%Y")
|
|
167
201
|
api_url = f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial={di_date}&dataFinal={di_date}"
|
|
168
|
-
response = requests.get(api_url, timeout=
|
|
202
|
+
response = requests.get(api_url, timeout=TIMEOUT)
|
|
169
203
|
response.raise_for_status()
|
|
170
204
|
|
|
171
205
|
if di_date not in response.text:
|
|
@@ -182,7 +216,7 @@ def _vna_lft(reference_date: pd.Timestamp) -> float:
|
|
|
182
216
|
url_file = f"{reference_date.strftime('%Y%m%d')}APC238"
|
|
183
217
|
url_vna = url_base + url_file
|
|
184
218
|
|
|
185
|
-
response = requests.get(url_vna, timeout=
|
|
219
|
+
response = requests.get(url_vna, timeout=TIMEOUT)
|
|
186
220
|
response.raise_for_status()
|
|
187
221
|
file_text = response.text
|
|
188
222
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.22.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
|