PYield 0.8.0__tar.gz → 0.8.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.8.0 → pyield-0.8.2}/PKG-INFO +13 -13
- {pyield-0.8.0 → pyield-0.8.2}/README.md +11 -11
- pyield-0.8.2/pyield/__about__.py +1 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/bday.py +16 -11
- {pyield-0.8.0 → pyield-0.8.2}/pyield/data_access.py +8 -8
- {pyield-0.8.0 → pyield-0.8.2}/pyield/futures/historical.py +1 -1
- {pyield-0.8.0 → pyield-0.8.2}/pyield/futures/intraday.py +42 -33
- {pyield-0.8.0 → pyield-0.8.2}/pyield/futures/xml.py +9 -3
- {pyield-0.8.0 → pyield-0.8.2}/pyield/holidays/core.py +10 -6
- {pyield-0.8.0 → pyield-0.8.2}/pyield/indicators.py +6 -5
- pyield-0.8.2/pyield/interpolator.py +91 -0
- pyield-0.8.2/pyield/ntnb.py +408 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/projections.py +1 -1
- {pyield-0.8.0 → pyield-0.8.2}/pyproject.toml +6 -2
- {pyield-0.8.0 → pyield-0.8.2}/tests/test_bday.py +12 -12
- {pyield-0.8.0 → pyield-0.8.2}/tests/test_futures.py +2 -2
- {pyield-0.8.0 → pyield-0.8.2}/tests/test_futures_xml.py +16 -16
- pyield-0.8.0/pyield/__about__.py +0 -1
- pyield-0.8.0/pyield/interpolator.py +0 -78
- pyield-0.8.0/pyield/ntnb.py +0 -261
- {pyield-0.8.0 → pyield-0.8.2}/LICENSE +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/__init__.py +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/date_validator.py +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/futures/__init__.py +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/futures/common.py +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/holidays/__init__.py +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/py.typed +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/spreads.py +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/pyield/tn_bonds.py +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/tests/__init__.py +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/tests/data/SPRD231222.zip +0 -0
- {pyield-0.8.0 → pyield-0.8.2}/tests/data/SPRD240426.zip +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PYield
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: A Python library for analysis of fixed income instruments in Brazil
|
|
5
5
|
Keywords: fixed-income, brazil, finance, analysis, bonds
|
|
6
6
|
Author-Email: Carlos Carvalho <cr.cj@outlook.com>
|
|
@@ -31,7 +31,7 @@ Classifier: Operating System :: OS Independent
|
|
|
31
31
|
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
32
32
|
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
33
33
|
Project-URL: Source, https://github.com/crdcj/PYield
|
|
34
|
-
Requires-Python: >=3.
|
|
34
|
+
Requires-Python: >=3.12
|
|
35
35
|
Requires-Dist: pandas>=2.0.0
|
|
36
36
|
Requires-Dist: numpy
|
|
37
37
|
Requires-Dist: beautifulsoup4
|
|
@@ -74,23 +74,23 @@ pip install pyield
|
|
|
74
74
|
|
|
75
75
|
# Count the number of business days between two dates
|
|
76
76
|
# Start date is included, end date is excluded
|
|
77
|
-
>>> yd.count_bdays(start='
|
|
77
|
+
>>> yd.count_bdays(start='29-12-2023', end='02-01-2024')
|
|
78
78
|
1
|
|
79
79
|
|
|
80
80
|
# Get the next business day after a given date (offset=1)
|
|
81
|
-
>>> yd.offset_bdays(dates="
|
|
81
|
+
>>> yd.offset_bdays(dates="29-12-2023", offset=1)
|
|
82
82
|
Timestamp('2024-01-02 00:00:00')
|
|
83
83
|
|
|
84
84
|
# Get the next business day if it is not a business day (offset=0)
|
|
85
|
-
>>> yd.offset_bdays(dates="
|
|
85
|
+
>>> yd.offset_bdays(dates="30-12-2023", offset=0)
|
|
86
86
|
Timestamp('2024-01-02 00:00:00')
|
|
87
87
|
|
|
88
88
|
# Since 2023-12-29 is a business day, it returns the same date (offset=0)
|
|
89
|
-
>>> yd.offset_bdays(dates="
|
|
89
|
+
>>> yd.offset_bdays(dates="29-12-2023", offset=0)
|
|
90
90
|
Timestamp('2023-12-29 00:00:00')
|
|
91
91
|
|
|
92
92
|
# Generate a pandas series with the business days between two dates
|
|
93
|
-
>>> yd.generate_bdays(start='
|
|
93
|
+
>>> yd.generate_bdays(start='29-12-2023', end='03-01-2024')
|
|
94
94
|
0 2023-12-29
|
|
95
95
|
1 2024-01-02
|
|
96
96
|
2 2024-01-03
|
|
@@ -111,7 +111,7 @@ TradeTime TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate La
|
|
|
111
111
|
2024-04-21 13:37:39 DI1F39 2039-01-03 3683 ... <NA> <NA> <NA> <NA>
|
|
112
112
|
|
|
113
113
|
# Fetch historical DI Futures data from B3
|
|
114
|
-
>>> yd.fetch_asset(asset_code="DI1", reference_date='
|
|
114
|
+
>>> yd.fetch_asset(asset_code="DI1", reference_date='08-03-2024')
|
|
115
115
|
TradeDate TickerSymbol ExpirationDate BDaysToExp ... LastRate LastAskRate LastBidRate SettlementRate
|
|
116
116
|
2024-03-08 DI1J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
|
|
117
117
|
2024-03-08 DI1K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
|
|
@@ -127,7 +127,7 @@ TradeDate TickerSymbol ExpirationDate BDaysToExp ... LastRate LastAskRate LastB
|
|
|
127
127
|
# Fetch a DataFrame with the NTN-B data from ANBIMA
|
|
128
128
|
# Anbima data is available for the last 5 working days
|
|
129
129
|
# Obs: Anbima members have access to the full history
|
|
130
|
-
>>> yd.fetch_asset(asset_code="NTN-B", reference_date='
|
|
130
|
+
>>> yd.fetch_asset(asset_code="NTN-B", reference_date='12-04-2024')
|
|
131
131
|
|
|
132
132
|
BondType ReferenceDate MaturityDate BidRate AskRate IndicativeRate Price
|
|
133
133
|
NTN-B 2024-04-12 2024-08-15 0.07540 0.07504 0.07523 4,271.43565
|
|
@@ -142,7 +142,7 @@ BondType ReferenceDate MaturityDate BidRate AskRate IndicativeRate Price
|
|
|
142
142
|
### Spreads Calculation
|
|
143
143
|
```python
|
|
144
144
|
# Calculate the spread between two DI Futures contracts and the pre-fix bonds
|
|
145
|
-
>>> yd.calculate_spreads(spread_type="
|
|
145
|
+
>>> yd.calculate_spreads(spread_type="di_pre", reference_date="11-04-2024")
|
|
146
146
|
|
|
147
147
|
BondType ReferenceDate MaturityDate DISpread
|
|
148
148
|
LTN 2024-04-11 2024-07-01 -20.28
|
|
@@ -157,15 +157,15 @@ BondType ReferenceDate MaturityDate DISpread
|
|
|
157
157
|
### Indicators Data
|
|
158
158
|
```python
|
|
159
159
|
# Fetch the SELIC target rate from the Central Bank of Brazil
|
|
160
|
-
>>> yd.fetch_indicator(indicator_code="SELIC", reference_date='
|
|
160
|
+
>>> yd.fetch_indicator(indicator_code="SELIC", reference_date='12-04-2024')
|
|
161
161
|
0.1075 # 10.75%
|
|
162
162
|
|
|
163
163
|
# Fetch the IPCA monthly inflation rate from IBGE
|
|
164
|
-
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='
|
|
164
|
+
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='18-03-2024')
|
|
165
165
|
0.16 # 0.16%
|
|
166
166
|
|
|
167
167
|
# If no data is yet available for the indicator, the function returns None
|
|
168
|
-
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='
|
|
168
|
+
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='10-04-2024')
|
|
169
169
|
None
|
|
170
170
|
```
|
|
171
171
|
|
|
@@ -31,23 +31,23 @@ pip install pyield
|
|
|
31
31
|
|
|
32
32
|
# Count the number of business days between two dates
|
|
33
33
|
# Start date is included, end date is excluded
|
|
34
|
-
>>> yd.count_bdays(start='
|
|
34
|
+
>>> yd.count_bdays(start='29-12-2023', end='02-01-2024')
|
|
35
35
|
1
|
|
36
36
|
|
|
37
37
|
# Get the next business day after a given date (offset=1)
|
|
38
|
-
>>> yd.offset_bdays(dates="
|
|
38
|
+
>>> yd.offset_bdays(dates="29-12-2023", offset=1)
|
|
39
39
|
Timestamp('2024-01-02 00:00:00')
|
|
40
40
|
|
|
41
41
|
# Get the next business day if it is not a business day (offset=0)
|
|
42
|
-
>>> yd.offset_bdays(dates="
|
|
42
|
+
>>> yd.offset_bdays(dates="30-12-2023", offset=0)
|
|
43
43
|
Timestamp('2024-01-02 00:00:00')
|
|
44
44
|
|
|
45
45
|
# Since 2023-12-29 is a business day, it returns the same date (offset=0)
|
|
46
|
-
>>> yd.offset_bdays(dates="
|
|
46
|
+
>>> yd.offset_bdays(dates="29-12-2023", offset=0)
|
|
47
47
|
Timestamp('2023-12-29 00:00:00')
|
|
48
48
|
|
|
49
49
|
# Generate a pandas series with the business days between two dates
|
|
50
|
-
>>> yd.generate_bdays(start='
|
|
50
|
+
>>> yd.generate_bdays(start='29-12-2023', end='03-01-2024')
|
|
51
51
|
0 2023-12-29
|
|
52
52
|
1 2024-01-02
|
|
53
53
|
2 2024-01-03
|
|
@@ -68,7 +68,7 @@ TradeTime TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate La
|
|
|
68
68
|
2024-04-21 13:37:39 DI1F39 2039-01-03 3683 ... <NA> <NA> <NA> <NA>
|
|
69
69
|
|
|
70
70
|
# Fetch historical DI Futures data from B3
|
|
71
|
-
>>> yd.fetch_asset(asset_code="DI1", reference_date='
|
|
71
|
+
>>> yd.fetch_asset(asset_code="DI1", reference_date='08-03-2024')
|
|
72
72
|
TradeDate TickerSymbol ExpirationDate BDaysToExp ... LastRate LastAskRate LastBidRate SettlementRate
|
|
73
73
|
2024-03-08 DI1J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
|
|
74
74
|
2024-03-08 DI1K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
|
|
@@ -84,7 +84,7 @@ TradeDate TickerSymbol ExpirationDate BDaysToExp ... LastRate LastAskRate LastB
|
|
|
84
84
|
# Fetch a DataFrame with the NTN-B data from ANBIMA
|
|
85
85
|
# Anbima data is available for the last 5 working days
|
|
86
86
|
# Obs: Anbima members have access to the full history
|
|
87
|
-
>>> yd.fetch_asset(asset_code="NTN-B", reference_date='
|
|
87
|
+
>>> yd.fetch_asset(asset_code="NTN-B", reference_date='12-04-2024')
|
|
88
88
|
|
|
89
89
|
BondType ReferenceDate MaturityDate BidRate AskRate IndicativeRate Price
|
|
90
90
|
NTN-B 2024-04-12 2024-08-15 0.07540 0.07504 0.07523 4,271.43565
|
|
@@ -99,7 +99,7 @@ BondType ReferenceDate MaturityDate BidRate AskRate IndicativeRate Price
|
|
|
99
99
|
### Spreads Calculation
|
|
100
100
|
```python
|
|
101
101
|
# Calculate the spread between two DI Futures contracts and the pre-fix bonds
|
|
102
|
-
>>> yd.calculate_spreads(spread_type="
|
|
102
|
+
>>> yd.calculate_spreads(spread_type="di_pre", reference_date="11-04-2024")
|
|
103
103
|
|
|
104
104
|
BondType ReferenceDate MaturityDate DISpread
|
|
105
105
|
LTN 2024-04-11 2024-07-01 -20.28
|
|
@@ -114,15 +114,15 @@ BondType ReferenceDate MaturityDate DISpread
|
|
|
114
114
|
### Indicators Data
|
|
115
115
|
```python
|
|
116
116
|
# Fetch the SELIC target rate from the Central Bank of Brazil
|
|
117
|
-
>>> yd.fetch_indicator(indicator_code="SELIC", reference_date='
|
|
117
|
+
>>> yd.fetch_indicator(indicator_code="SELIC", reference_date='12-04-2024')
|
|
118
118
|
0.1075 # 10.75%
|
|
119
119
|
|
|
120
120
|
# Fetch the IPCA monthly inflation rate from IBGE
|
|
121
|
-
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='
|
|
121
|
+
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='18-03-2024')
|
|
122
122
|
0.16 # 0.16%
|
|
123
123
|
|
|
124
124
|
# If no data is yet available for the indicator, the function returns None
|
|
125
|
-
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='
|
|
125
|
+
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='10-04-2024')
|
|
126
126
|
None
|
|
127
127
|
```
|
|
128
128
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.2"
|
|
@@ -57,9 +57,9 @@ def is_bday(date: SingleDateTypes | None = None) -> bool:
|
|
|
57
57
|
bool: True if the input date is a business day, False otherwise.
|
|
58
58
|
|
|
59
59
|
Examples:
|
|
60
|
-
>>> yd.is_bday(
|
|
60
|
+
>>> yd.is_bday("2023-12-25") # Christmas
|
|
61
61
|
False
|
|
62
|
-
>>> yd.is_bday()
|
|
62
|
+
>>> yd.is_bday() # Check if today is a business day
|
|
63
63
|
True
|
|
64
64
|
"""
|
|
65
65
|
normalized_date = _normalize_input_dates(date)
|
|
@@ -140,11 +140,11 @@ def offset_bdays(
|
|
|
140
140
|
`Series` of offset dates.
|
|
141
141
|
|
|
142
142
|
Examples:
|
|
143
|
-
>>> date =
|
|
143
|
+
>>> date = "2023-12-23" # Saturday before Christmas
|
|
144
144
|
>>> yd.offset_bdays(date, 0)
|
|
145
145
|
Timestamp('2023-12-26')
|
|
146
146
|
|
|
147
|
-
>>> date =
|
|
147
|
+
>>> date = "2023-12-22" # Friday before Christmas
|
|
148
148
|
>>> yd.offset_bdays(date, 0)
|
|
149
149
|
Timestamp('2023-12-22') # No offset because it's a business day
|
|
150
150
|
|
|
@@ -172,10 +172,15 @@ def offset_bdays(
|
|
|
172
172
|
dates_np, offsets=offset, roll=roll, holidays=selected_holidays_np
|
|
173
173
|
)
|
|
174
174
|
if isinstance(offsetted_dates_np, np.datetime64):
|
|
175
|
-
|
|
175
|
+
result = pd.Timestamp(offsetted_dates_np)
|
|
176
|
+
# Force to datetime[ns] if the input was a single date
|
|
177
|
+
result = result.as_unit("ns")
|
|
176
178
|
else:
|
|
177
|
-
result = pd.to_datetime(offsetted_dates_np
|
|
178
|
-
|
|
179
|
+
result = pd.to_datetime(offsetted_dates_np)
|
|
180
|
+
# Force the result to be a Series if the input was not a single date
|
|
181
|
+
result = pd.Series(result).astype("datetime64[ns]")
|
|
182
|
+
|
|
183
|
+
return result
|
|
179
184
|
|
|
180
185
|
|
|
181
186
|
@overload
|
|
@@ -252,11 +257,11 @@ def count_bdays(
|
|
|
252
257
|
https://numpy.org/doc/stable/reference/generated/numpy.busday_count.html.
|
|
253
258
|
|
|
254
259
|
Examples:
|
|
255
|
-
>>> yd.count_bdays(
|
|
260
|
+
>>> yd.count_bdays("2023-12-15", "2024-01-01")
|
|
256
261
|
10
|
|
257
262
|
|
|
258
|
-
>>> start =
|
|
259
|
-
>>> end = pd.to_datetime([
|
|
263
|
+
>>> start = "2023-01-01"
|
|
264
|
+
>>> end = pd.to_datetime(["2023-01-31", "2023-03-01"])
|
|
260
265
|
>>> yd.count_bdays(start, end)
|
|
261
266
|
pd.Series([22, 40], dtype='int64')
|
|
262
267
|
"""
|
|
@@ -315,7 +320,7 @@ def generate_bdays(
|
|
|
315
320
|
start and end dates, considering the specified holidays.
|
|
316
321
|
|
|
317
322
|
Examples:
|
|
318
|
-
>>> yd.generate_bdays(start=
|
|
323
|
+
>>> yd.generate_bdays(start="2023-12-22", end="2024-01-02")
|
|
319
324
|
pd.Series(['2023-12-22', '2023-12-26', '2023-12-27', '2023-12-28', '2023-12-29',
|
|
320
325
|
'2024-01-02'], dtype='datetime64[ns]')
|
|
321
326
|
|
|
@@ -42,8 +42,8 @@ def fetch_asset(
|
|
|
42
42
|
ValueError: If the asset code is not recognized or supported.
|
|
43
43
|
|
|
44
44
|
Examples:
|
|
45
|
-
>>> fetch_asset(
|
|
46
|
-
>>> fetch_asset(
|
|
45
|
+
>>> fetch_asset("TRB", "31-05-2024")
|
|
46
|
+
>>> fetch_asset("DI1", "31-05-2024")
|
|
47
47
|
"""
|
|
48
48
|
SUPPORTED_BONDS = ["LTN", "LFT", "NTN-F", "NTN-B"]
|
|
49
49
|
SUPPORTED_FUTURES = ["DI1", "DDI", "FRC", "DAP", "DOL", "WDO", "IND", "WIN"]
|
|
@@ -99,11 +99,11 @@ def fetch_indicator(
|
|
|
99
99
|
ValueError: If the indicator code is not recognized or supported.
|
|
100
100
|
|
|
101
101
|
Examples:
|
|
102
|
-
>>> fetch_indicator(
|
|
102
|
+
>>> fetch_indicator("SELIC", "31-05-2024")
|
|
103
103
|
0.1075 # Indicates a SELIC target rate of 10.75% p.a.
|
|
104
|
-
>>> fetch_indicator(
|
|
104
|
+
>>> fetch_indicator("IPCA", "01-04-2024")
|
|
105
105
|
0.0038 # Indicates an IPCA monthly rate of 0.38% p.m.
|
|
106
|
-
>>> fetch_indicator(
|
|
106
|
+
>>> fetch_indicator("DI", "31-05-2024")
|
|
107
107
|
0.00040168 # Indicates a DI daily rate of 0.02% p.d.
|
|
108
108
|
"""
|
|
109
109
|
normalized_date = dv.normalize_date(reference_date)
|
|
@@ -142,7 +142,7 @@ def fetch_projection(projection_code: str) -> pr.IndicatorProjection:
|
|
|
142
142
|
ValueError: If the projection code is not recognized or supported.
|
|
143
143
|
|
|
144
144
|
Examples:
|
|
145
|
-
>>> fetch_projection(
|
|
145
|
+
>>> fetch_projection("IPCA_CM")
|
|
146
146
|
IndicatorProjection(
|
|
147
147
|
last_updated=pd.Timestamp('2024-04-19 18:55:00'),
|
|
148
148
|
reference_month_ts=pd.Timestamp('2024-04-01 00:00:00'),
|
|
@@ -166,7 +166,7 @@ def calculate_spreads(
|
|
|
166
166
|
|
|
167
167
|
Parameters:
|
|
168
168
|
spread_type (str): The type of spread to calculate. Available options are:
|
|
169
|
-
- "
|
|
169
|
+
- "DI_PRE": the spread between DI Futures and Treasury Pre-Fixed bonds.
|
|
170
170
|
reference_date (str | pd.Timestamp, optional): The reference date for the
|
|
171
171
|
spread calculation. If None or not provided, defaults to the previous
|
|
172
172
|
business day according to the Brazilian calendar.
|
|
@@ -180,7 +180,7 @@ def calculate_spreads(
|
|
|
180
180
|
"""
|
|
181
181
|
# Normalize the reference date
|
|
182
182
|
normalized_date = dv.normalize_date(reference_date)
|
|
183
|
-
if spread_type.upper() == "
|
|
183
|
+
if spread_type.upper() == "DI_PRE":
|
|
184
184
|
return sp.calculate_di_spreads(normalized_date)
|
|
185
185
|
else:
|
|
186
186
|
raise ValueError("Invalid spread type.")
|
|
@@ -119,7 +119,7 @@ def fetch_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
119
119
|
url_date = trade_date.strftime("%d/%m/%Y")
|
|
120
120
|
# url example: https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data=05/10/2023&Mercadoria=DI1
|
|
121
121
|
url = f"https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data={url_date}&Mercadoria={asset_code}&XLS=false"
|
|
122
|
-
r = requests.get(url)
|
|
122
|
+
r = requests.get(url, timeout=10)
|
|
123
123
|
|
|
124
124
|
text = r.text
|
|
125
125
|
if "VENCTO" not in text:
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
+
from io import StringIO
|
|
2
|
+
|
|
1
3
|
import pandas as pd
|
|
2
4
|
import requests
|
|
3
5
|
|
|
4
6
|
from .. import bday
|
|
5
7
|
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
# Função para salvar DataFrame em CSV e ler com read_csv
|
|
10
|
+
def convert_with_read_csv(df):
|
|
11
|
+
buffer = StringIO()
|
|
12
|
+
df.to_csv(buffer, index=False)
|
|
13
|
+
buffer.seek(0) # Reposiciona o cursor no início do buffer
|
|
14
|
+
return pd.read_csv(buffer, dtype_backend="numpy_nullable")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _fetch_b3_df(future_code: str) -> pd.DataFrame:
|
|
8
18
|
"""
|
|
9
19
|
Fetch the latest data for a given future code from B3 derivatives quotation API.
|
|
10
20
|
|
|
@@ -21,47 +31,21 @@ def _fetch_raw_df(future_code: str) -> pd.DataFrame:
|
|
|
21
31
|
url = f"https://cotacao.b3.com.br/mds/api/v1/DerivativeQuotation/{future_code}"
|
|
22
32
|
|
|
23
33
|
try:
|
|
24
|
-
r = requests.get(url)
|
|
34
|
+
r = requests.get(url, timeout=10)
|
|
25
35
|
r.raise_for_status() # Check for HTTP request errors
|
|
26
36
|
except requests.exceptions.RequestException:
|
|
27
37
|
raise Exception(f"Failed to fetch data for {future_code}.") from None
|
|
28
38
|
|
|
29
39
|
r.encoding = "utf-8" # Explicitly set response encoding to utf-8 for consistency
|
|
30
40
|
|
|
31
|
-
if "Quotation not available" in r.text:
|
|
41
|
+
if "Quotation not available" in r.text or "curPrc" not in r.text:
|
|
32
42
|
return pd.DataFrame()
|
|
33
43
|
|
|
34
44
|
# Normalize JSON response into a flat table
|
|
35
45
|
df = pd.json_normalize(r.json()["Scty"])
|
|
36
46
|
|
|
37
|
-
# Clean and reformat the DataFrame columns
|
|
38
|
-
df.columns = (df.columns
|
|
39
|
-
.str.replace("SctyQtn.", "")
|
|
40
|
-
.str.replace("asset.AsstSummry.", "")
|
|
41
|
-
) # fmt: skip
|
|
42
|
-
df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
|
|
43
|
-
|
|
44
|
-
# Convert maturity codes to datetime and drop rows with missing values
|
|
45
|
-
df["mtrtyCode"] = pd.to_datetime(df["mtrtyCode"], errors="coerce")
|
|
46
|
-
df.dropna(subset=["mtrtyCode"], inplace=True)
|
|
47
|
-
|
|
48
|
-
# Sort the DataFrame by maturity code and reset the index
|
|
49
|
-
df.sort_values("mtrtyCode", inplace=True, ignore_index=True)
|
|
50
|
-
|
|
51
|
-
# Get currante date
|
|
52
|
-
today = pd.Timestamp.now().normalize()
|
|
53
|
-
df["TradeDate"] = today
|
|
54
|
-
|
|
55
|
-
# Get current date and time
|
|
56
|
-
now = pd.Timestamp.now().round("s")
|
|
57
|
-
# Subtract 15 minutes from the current time to account for API delay
|
|
58
|
-
trade_ts = now - pd.Timedelta(minutes=15)
|
|
59
|
-
df["TradeTime"] = trade_ts
|
|
60
|
-
|
|
61
47
|
# Convert DataFrame to use nullable data types for better type consistency
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return df
|
|
48
|
+
return convert_with_read_csv(df)
|
|
65
49
|
|
|
66
50
|
|
|
67
51
|
def _rename_columns(df: pd.DataFrame) -> pd.DataFrame:
|
|
@@ -101,6 +85,32 @@ def _rename_columns(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
101
85
|
def _process_df(raw_df: pd.DataFrame) -> pd.DataFrame:
|
|
102
86
|
df = raw_df.copy()
|
|
103
87
|
|
|
88
|
+
# Clean and reformat the DataFrame columns
|
|
89
|
+
df.columns = (df.columns
|
|
90
|
+
.str.replace("SctyQtn.", "")
|
|
91
|
+
.str.replace("asset.AsstSummry.", "")
|
|
92
|
+
) # fmt: skip
|
|
93
|
+
df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
|
|
94
|
+
|
|
95
|
+
df = _rename_columns(df)
|
|
96
|
+
|
|
97
|
+
# Convert maturity codes to datetime and drop rows with missing values
|
|
98
|
+
df["ExpirationDate"] = pd.to_datetime(df["ExpirationDate"], errors="coerce")
|
|
99
|
+
df.dropna(subset=["ExpirationDate"], inplace=True)
|
|
100
|
+
|
|
101
|
+
# Sort the DataFrame by maturity code and reset the index
|
|
102
|
+
df.sort_values("ExpirationDate", inplace=True, ignore_index=True)
|
|
103
|
+
|
|
104
|
+
# Get currante date
|
|
105
|
+
today = pd.Timestamp.now().normalize()
|
|
106
|
+
df["TradeDate"] = today
|
|
107
|
+
|
|
108
|
+
# Get current date and time
|
|
109
|
+
now = pd.Timestamp.now().round("s")
|
|
110
|
+
# Subtract 15 minutes from the current time to account for API delay
|
|
111
|
+
trade_ts = now - pd.Timedelta(minutes=15)
|
|
112
|
+
df["TradeTime"] = trade_ts
|
|
113
|
+
|
|
104
114
|
df["BDaysToExp"] = bday.count_bdays(df["TradeDate"], df["ExpirationDate"])
|
|
105
115
|
|
|
106
116
|
df["DaysToExp"] = (df["ExpirationDate"] - df["TradeDate"]).dt.days
|
|
@@ -156,10 +166,9 @@ def fetch_intraday_df(future_code: str) -> pd.DataFrame:
|
|
|
156
166
|
Returns:
|
|
157
167
|
pd.DataFrame: A Pandas pd.DataFrame containing the latest DI futures data.
|
|
158
168
|
"""
|
|
159
|
-
raw_df =
|
|
169
|
+
raw_df = _fetch_b3_df(future_code)
|
|
160
170
|
if raw_df.empty:
|
|
161
171
|
return raw_df
|
|
162
|
-
df =
|
|
163
|
-
df = _process_df(df)
|
|
172
|
+
df = _process_df(raw_df)
|
|
164
173
|
df = _select_and_reorder_columns(df)
|
|
165
174
|
return df
|
|
@@ -43,7 +43,7 @@ def _get_file_from_url(trade_date: pd.Timestamp, source_type: str) -> io.BytesIO
|
|
|
43
43
|
else:
|
|
44
44
|
raise ValueError("Invalid source type. Must be either 'PR' or 'SPR'.")
|
|
45
45
|
|
|
46
|
-
response = requests.get(url)
|
|
46
|
+
response = requests.get(url, timeout=10)
|
|
47
47
|
|
|
48
48
|
# File will be considered invalid if it is too small
|
|
49
49
|
if response.status_code != 200 or len(response.content) < 1024:
|
|
@@ -74,9 +74,15 @@ def _extract_xml_from_zip(zip_file: io.BytesIO) -> io.BytesIO:
|
|
|
74
74
|
|
|
75
75
|
def _extract_data_from_xml(xml_file: io.BytesIO, asset_code: str) -> list[dict]:
|
|
76
76
|
parser = etree.XMLParser(
|
|
77
|
-
ns_clean=True,
|
|
77
|
+
ns_clean=True,
|
|
78
|
+
remove_blank_text=True,
|
|
79
|
+
remove_comments=True,
|
|
80
|
+
recover=True,
|
|
81
|
+
resolve_entities=False,
|
|
82
|
+
no_network=True,
|
|
83
|
+
load_dtd=False, # Disable DTD loading
|
|
78
84
|
)
|
|
79
|
-
tree = etree.parse(xml_file, parser)
|
|
85
|
+
tree = etree.parse(xml_file, parser=parser)
|
|
80
86
|
|
|
81
87
|
# XPath para encontrar elementos cujo texto começa com código do ativo: DI1, FRC...
|
|
82
88
|
namespaces = {"ns": "urn:bvmf.217.01.xsd"}
|
|
@@ -16,10 +16,13 @@ class BrHolidays:
|
|
|
16
16
|
self.old_holidays = self._load_holidays(old_holidays_path)
|
|
17
17
|
|
|
18
18
|
def _load_holidays(self, file_path: Path) -> pd.Series:
|
|
19
|
-
"""
|
|
19
|
+
"""
|
|
20
|
+
Loads the holidays from a text file and returns it as a Series of Timestamps.
|
|
21
|
+
"""
|
|
20
22
|
df = pd.read_csv(file_path, header=None, names=["date"], comment="#")
|
|
21
23
|
# Convert the dates to a pd.Series of pd.Timestamp
|
|
22
|
-
|
|
24
|
+
holidays = pd.to_datetime(df["date"], format="%d/%m/%Y")
|
|
25
|
+
return holidays.astype("datetime64[ns]")
|
|
23
26
|
|
|
24
27
|
def get_applicable_holidays(
|
|
25
28
|
self,
|
|
@@ -27,13 +30,14 @@ class BrHolidays:
|
|
|
27
30
|
holiday_list: Literal["old", "new", "infer"] = "infer",
|
|
28
31
|
) -> pd.Series:
|
|
29
32
|
"""
|
|
30
|
-
Returns the correct list of holidays to use based on the most recent date in
|
|
33
|
+
Returns the correct list of holidays to use based on the most recent date in
|
|
34
|
+
the input.
|
|
31
35
|
|
|
32
36
|
Args:
|
|
33
37
|
dates (pd.Timestamp | pd.Series): The date(s) to use to infer the holidays.
|
|
34
|
-
holiday_list (str): The holidays list to use. Valid options are 'old', 'new'
|
|
35
|
-
'infer'. If 'infer' is used, the list of holidays is selected based
|
|
36
|
-
earliest (minimum) date in the input.
|
|
38
|
+
holiday_list (str): The holidays list to use. Valid options are 'old', 'new'
|
|
39
|
+
or 'infer'. If 'infer' is used, the list of holidays is selected based
|
|
40
|
+
on the earliest (minimum) date in the input.
|
|
37
41
|
|
|
38
42
|
Returns:
|
|
39
43
|
pd.Series: The list of holidays to use.
|
|
@@ -30,7 +30,7 @@ def fetch_ipca_mr(reference_date: pd.Timestamp) -> float | None:
|
|
|
30
30
|
api_url = f"https://servicodados.ibge.gov.br/api/v3/agregados/6691/periodos/{ipca_date}/variaveis/63?localidades=N1[all]"
|
|
31
31
|
|
|
32
32
|
# Send a GET request to the API
|
|
33
|
-
response = requests.get(api_url)
|
|
33
|
+
response = requests.get(api_url, timeout=10)
|
|
34
34
|
|
|
35
35
|
# Raises HTTPError, if one occurred
|
|
36
36
|
response.raise_for_status()
|
|
@@ -50,7 +50,7 @@ def fetch_selic_target(reference_date: pd.Timestamp) -> float | None:
|
|
|
50
50
|
# https://api.bcb.gov.br/dados/serie/bcdata.sgs.432/dados?formato=json&dataInicial=12/04/2024&dataFinal=12/04/2024
|
|
51
51
|
selic_date = reference_date.strftime("%d/%m/%Y")
|
|
52
52
|
api_url = f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.432/dados?formato=json&dataInicial={selic_date}&dataFinal={selic_date}"
|
|
53
|
-
response = requests.get(api_url)
|
|
53
|
+
response = requests.get(api_url, timeout=10)
|
|
54
54
|
response.raise_for_status()
|
|
55
55
|
|
|
56
56
|
if selic_date in response.text:
|
|
@@ -64,7 +64,7 @@ def fetch_di(reference_date: pd.Timestamp) -> float | None:
|
|
|
64
64
|
# https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial=12/04/2024&dataFinal=12/04/2024
|
|
65
65
|
di_date = reference_date.strftime("%d/%m/%Y")
|
|
66
66
|
api_url = f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial={di_date}&dataFinal={di_date}"
|
|
67
|
-
response = requests.get(api_url)
|
|
67
|
+
response = requests.get(api_url, timeout=10)
|
|
68
68
|
response.raise_for_status()
|
|
69
69
|
|
|
70
70
|
if di_date in response.text:
|
|
@@ -78,9 +78,10 @@ def fetch_vna_selic(reference_date: pd.Timestamp) -> float | None:
|
|
|
78
78
|
# url example: https://www3.bcb.gov.br/novoselic/rest/arquivosDiarios/pub/download/3/20240418APC238
|
|
79
79
|
|
|
80
80
|
url_base = "https://www3.bcb.gov.br/novoselic/rest/arquivosDiarios/pub/download/3/"
|
|
81
|
-
url_file = f"{reference_date.strftime(
|
|
81
|
+
url_file = f"{reference_date.strftime('%Y%m%d')}APC238"
|
|
82
82
|
url_vna = url_base + url_file
|
|
83
|
-
|
|
83
|
+
|
|
84
|
+
response = requests.get(url_vna, timeout=10)
|
|
84
85
|
response.raise_for_status()
|
|
85
86
|
file_text = response.text
|
|
86
87
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import bisect
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def interpolate_flat_forward(
|
|
5
|
+
prev_rate: float, prev_bdays: int, next_rate: float, next_bdays: int, bdays: int
|
|
6
|
+
) -> float:
|
|
7
|
+
"""Performs interest rate interpolation using the flat forward interpolation method
|
|
8
|
+
considering a base of 252 business days.
|
|
9
|
+
|
|
10
|
+
The interpolation is done between two known pairs of vertices (prev_rate,
|
|
11
|
+
prev_business_days) and (next_rate, next_business_days), for a third vertex defined
|
|
12
|
+
by the number of business days 'business_days' where 'prev_business_days <
|
|
13
|
+
business_days < next_business_days'. This third vertex is the point in time for
|
|
14
|
+
which the interest rate is being calculated.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
- prev_rate (float): Interest rate of the previous vertex.
|
|
18
|
+
- prev_bdays (int): Number of business days of the previous vertex.
|
|
19
|
+
- next_rate (float): Interest rate of the next vertex.
|
|
20
|
+
- next_bdays (int): Number of business days of the next vertex.
|
|
21
|
+
- bdays (int): Number of business days for which the interest rate is to be
|
|
22
|
+
interpolated.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
interpolated_rate = interpolate_flat_forward(0.045, 30, 0.05, 60, 45)
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
float: The interpolated interest rate at the given `business_days`.
|
|
29
|
+
"""
|
|
30
|
+
a = (1 + prev_rate) ** (prev_bdays / 252)
|
|
31
|
+
b = (1 + next_rate) ** (next_bdays / 252)
|
|
32
|
+
c = (bdays - prev_bdays) / (next_bdays - prev_bdays)
|
|
33
|
+
|
|
34
|
+
return (a * (b / a) ** c) ** (252 / bdays) - 1
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def find_and_interpolate_flat_forward(
|
|
38
|
+
bdays: int,
|
|
39
|
+
known_bdays: list[int],
|
|
40
|
+
known_rates: list[float],
|
|
41
|
+
) -> float:
|
|
42
|
+
"""
|
|
43
|
+
Finds the appropriate interpolation point and returns the interest rate
|
|
44
|
+
interpolated by the flat forward method from that point. Uses the `bisect` module
|
|
45
|
+
for binary search in ordered lists.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
business_days (int): Number of business days for which the flat forward interest
|
|
50
|
+
rate is to be calculated.
|
|
51
|
+
known_business_days (List[int]): List of business days where interest rates are
|
|
52
|
+
known.
|
|
53
|
+
known_rates (List[float]): List of known interest rates.
|
|
54
|
+
|
|
55
|
+
Notes:
|
|
56
|
+
- It is assumed that `known_business_days` and `known_rates` are sorted and have
|
|
57
|
+
the same size.
|
|
58
|
+
- The method uses 252 business days per year in the interpolation, which is the
|
|
59
|
+
standard in the Brazilian market.
|
|
60
|
+
- Special cases are handled for situations where `business_days` is less than
|
|
61
|
+
the first known business day, greater than the last known business day, or
|
|
62
|
+
exactly equal to a known business day, avoiding the need for interpolation.
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
known_bdays = [30, 60, 90]
|
|
66
|
+
known_rates = [0.045, 0.05, 0.055]
|
|
67
|
+
interpolated_rate = find_and_interpolate_flat_forward(45, known_bdays, known_rates)
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
float: The interest rate interpolated by the flat forward method for the given
|
|
71
|
+
number of business days.
|
|
72
|
+
"""
|
|
73
|
+
# Special cases
|
|
74
|
+
if bdays <= known_bdays[0]:
|
|
75
|
+
return known_rates[0]
|
|
76
|
+
elif bdays >= known_bdays[-1]:
|
|
77
|
+
return known_rates[-1]
|
|
78
|
+
# Do not interpolate vertex whose rate is known
|
|
79
|
+
elif bdays in known_bdays:
|
|
80
|
+
return known_rates[known_bdays.index(bdays)]
|
|
81
|
+
|
|
82
|
+
# Find i such that known_business_days[i-1] < business_days < known_business_days[i]
|
|
83
|
+
i = bisect.bisect_left(known_bdays, bdays)
|
|
84
|
+
|
|
85
|
+
return interpolate_flat_forward(
|
|
86
|
+
known_rates[i - 1],
|
|
87
|
+
known_bdays[i - 1],
|
|
88
|
+
known_rates[i],
|
|
89
|
+
known_bdays[i],
|
|
90
|
+
bdays,
|
|
91
|
+
)
|