PYield 0.28.0__tar.gz → 0.28.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.28.0 → pyield-0.28.2}/PKG-INFO +5 -6
- {pyield-0.28.0 → pyield-0.28.2}/README.md +3 -3
- pyield-0.28.2/pyield/__about__.py +1 -0
- {pyield-0.28.0/pyield/b3_futures → pyield-0.28.2/pyield/anbima}/__init__.py +5 -5
- {pyield-0.28.0 → pyield-0.28.2}/pyield/anbima/tpf.py +16 -18
- {pyield-0.28.0/pyield/anbima → pyield-0.28.2/pyield/b3_futures}/__init__.py +5 -5
- {pyield-0.28.0 → pyield-0.28.2}/pyield/b3_futures/di.py +27 -27
- {pyield-0.28.0 → pyield-0.28.2}/pyield/b3_futures/historical.py +17 -19
- {pyield-0.28.0 → pyield-0.28.2}/pyield/b3_futures/xml.py +13 -11
- {pyield-0.28.0 → pyield-0.28.2}/pyield/indicators.py +19 -19
- {pyield-0.28.0 → pyield-0.28.2}/pyield/projections.py +9 -21
- {pyield-0.28.0 → pyield-0.28.2}/pyield/tpf/lft.py +6 -6
- {pyield-0.28.0 → pyield-0.28.2}/pyield/tpf/ltn.py +14 -14
- {pyield-0.28.0 → pyield-0.28.2}/pyield/tpf/ntnb.py +6 -6
- {pyield-0.28.0 → pyield-0.28.2}/pyield/tpf/ntnf.py +17 -28
- {pyield-0.28.0 → pyield-0.28.2}/pyield/tpf/tools.py +4 -4
- {pyield-0.28.0 → pyield-0.28.2}/pyproject.toml +2 -3
- pyield-0.28.0/pyield/__about__.py +0 -1
- {pyield-0.28.0 → pyield-0.28.2}/.gitignore +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/LICENSE +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/__init__.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/anbima/ima.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/anbima/imaq.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/b3_futures/common.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/b3_futures/intraday.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/bc/auction.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/bc/ptax.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/bc/repos.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/bday.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/conftest.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/data_cache.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/date_converter.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/holidays/__init__.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/holidays/brholidays.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/interpolator.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/py.typed +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/tools.py +0 -0
- {pyield-0.28.0 → pyield-0.28.2}/pyield/tpf/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PYield
|
|
3
|
-
Version: 0.28.
|
|
3
|
+
Version: 0.28.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,8 +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[html,parquet]>=2.0.0
|
|
38
|
-
Requires-Dist: python-calamine>=0.2.0
|
|
37
|
+
Requires-Dist: pandas[excel,html,parquet]>=2.0.0
|
|
39
38
|
Requires-Dist: requests>=2.31.0
|
|
40
39
|
Description-Content-Type: text/markdown
|
|
41
40
|
|
|
@@ -180,7 +179,7 @@ LastUpdatee TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate
|
|
|
180
179
|
2024-04-21 13:37:39 DI1F39 2039-01-03 3683 ... <NA> <NA> <NA> <NA>
|
|
181
180
|
|
|
182
181
|
# Fetch historical DI Futures data from B3
|
|
183
|
-
>>> yd.futures(contract_code="DI1",
|
|
182
|
+
>>> yd.futures(contract_code="DI1", date='08-03-2024')
|
|
184
183
|
TradeDate TickerSymbol ExpirationDate BDaysToExp ... LastRate LastAskRate LastBidRate SettlementRate
|
|
185
184
|
2024-03-08 DI1J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
|
|
186
185
|
2024-03-08 DI1K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
|
|
@@ -195,11 +194,11 @@ TradeDate TickerSymbol ExpirationDate BDaysToExp ... LastRate LastAskRate LastB
|
|
|
195
194
|
```python
|
|
196
195
|
>>> import pyield as yd
|
|
197
196
|
# Fetch the SELIC target rate from the Central Bank of Brazil
|
|
198
|
-
>>> yd.indicator(indicator_code="SELIC_TARGET",
|
|
197
|
+
>>> yd.indicator(indicator_code="SELIC_TARGET", date='12-04-2024')
|
|
199
198
|
0.1075 # 10.75%
|
|
200
199
|
|
|
201
200
|
# Fetch the IPCA monthly inflation rate from IBGE
|
|
202
|
-
>>> yd.indicator(indicator_code="IPCA_MR",
|
|
201
|
+
>>> yd.indicator(indicator_code="IPCA_MR", date='18-03-2024')
|
|
203
202
|
0.0016 # 0.16%
|
|
204
203
|
```
|
|
205
204
|
|
|
@@ -139,7 +139,7 @@ LastUpdatee TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate
|
|
|
139
139
|
2024-04-21 13:37:39 DI1F39 2039-01-03 3683 ... <NA> <NA> <NA> <NA>
|
|
140
140
|
|
|
141
141
|
# Fetch historical DI Futures data from B3
|
|
142
|
-
>>> yd.futures(contract_code="DI1",
|
|
142
|
+
>>> yd.futures(contract_code="DI1", date='08-03-2024')
|
|
143
143
|
TradeDate TickerSymbol ExpirationDate BDaysToExp ... LastRate LastAskRate LastBidRate SettlementRate
|
|
144
144
|
2024-03-08 DI1J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
|
|
145
145
|
2024-03-08 DI1K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
|
|
@@ -154,11 +154,11 @@ TradeDate TickerSymbol ExpirationDate BDaysToExp ... LastRate LastAskRate LastB
|
|
|
154
154
|
```python
|
|
155
155
|
>>> import pyield as yd
|
|
156
156
|
# Fetch the SELIC target rate from the Central Bank of Brazil
|
|
157
|
-
>>> yd.indicator(indicator_code="SELIC_TARGET",
|
|
157
|
+
>>> yd.indicator(indicator_code="SELIC_TARGET", date='12-04-2024')
|
|
158
158
|
0.1075 # 10.75%
|
|
159
159
|
|
|
160
160
|
# Fetch the IPCA monthly inflation rate from IBGE
|
|
161
|
-
>>> yd.indicator(indicator_code="IPCA_MR",
|
|
161
|
+
>>> yd.indicator(indicator_code="IPCA_MR", date='18-03-2024')
|
|
162
162
|
0.0016 # 0.16%
|
|
163
163
|
```
|
|
164
164
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.28.2"
|
|
@@ -15,7 +15,7 @@ ContractOptions = Literal["DI1", "DDI", "FRC", "DAP", "DOL", "WDO", "IND", "WIN"
|
|
|
15
15
|
|
|
16
16
|
def futures(
|
|
17
17
|
contract_code: ContractOptions,
|
|
18
|
-
|
|
18
|
+
date: DateScalar,
|
|
19
19
|
) -> pd.DataFrame:
|
|
20
20
|
"""
|
|
21
21
|
Fetches data for a specified futures contract based on type and reference date.
|
|
@@ -31,7 +31,7 @@ def futures(
|
|
|
31
31
|
- "WDO": Mini U.S. Dollar Futures from B3.
|
|
32
32
|
- "IND": Ibovespa Futures from B3.
|
|
33
33
|
- "WIN": Mini Ibovespa Futures from B3.
|
|
34
|
-
|
|
34
|
+
date (DateScalar): The date for which to fetch the data.
|
|
35
35
|
If the reference date is a string, it should be in 'DD-MM-YYYY' format.
|
|
36
36
|
|
|
37
37
|
Returns:
|
|
@@ -58,14 +58,14 @@ def futures(
|
|
|
58
58
|
...
|
|
59
59
|
"""
|
|
60
60
|
selected_contract = str(contract_code).upper()
|
|
61
|
-
|
|
61
|
+
converted_date = dc.convert_input_dates(date)
|
|
62
62
|
|
|
63
63
|
# First, try to fetch historical data for the specified date
|
|
64
|
-
df = fetch_historical_df(selected_contract,
|
|
64
|
+
df = fetch_historical_df(selected_contract, converted_date)
|
|
65
65
|
|
|
66
66
|
bz_today = dt.datetime.now(TIMEZONE_BZ).date()
|
|
67
67
|
# If there is no historical data available, try to fetch intraday data
|
|
68
|
-
if
|
|
68
|
+
if converted_date.date() == bz_today and df.empty:
|
|
69
69
|
df = fetch_intraday_df(selected_contract)
|
|
70
70
|
|
|
71
71
|
return df
|
|
@@ -19,9 +19,9 @@ ANBIMA_MEMBER_URL = "http://www.anbima.associados.rtm/merc_sec/arqs"
|
|
|
19
19
|
FORMAT_CHANGE_DATE = pd.to_datetime("13-05-2014", dayfirst=True)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def _get_file_content(
|
|
23
|
-
url_date =
|
|
24
|
-
if
|
|
22
|
+
def _get_file_content(date: pd.Timestamp) -> str:
|
|
23
|
+
url_date = date.strftime("%y%m%d")
|
|
24
|
+
if date < FORMAT_CHANGE_DATE:
|
|
25
25
|
filename = f"ms{url_date}.exe"
|
|
26
26
|
else:
|
|
27
27
|
filename = f"ms{url_date}.txt"
|
|
@@ -41,7 +41,7 @@ def _get_file_content(reference_date: pd.Timestamp) -> str:
|
|
|
41
41
|
# Checks if the response was successful (status code 200)
|
|
42
42
|
r.raise_for_status()
|
|
43
43
|
|
|
44
|
-
if
|
|
44
|
+
if date < FORMAT_CHANGE_DATE:
|
|
45
45
|
zip_file = zp.ZipFile(io.BytesIO(r.content))
|
|
46
46
|
file_content = zip_file.read(zip_file.namelist()[0]).decode("latin-1")
|
|
47
47
|
else:
|
|
@@ -106,9 +106,7 @@ def _process_raw_df(df_raw: pd.DataFrame) -> pd.DataFrame:
|
|
|
106
106
|
return df.sort_values(["BondType", "MaturityDate"], ignore_index=True)
|
|
107
107
|
|
|
108
108
|
|
|
109
|
-
def anbima_tpf_data(
|
|
110
|
-
reference_date: DateScalar, bond_type: str | None = None
|
|
111
|
-
) -> pd.DataFrame:
|
|
109
|
+
def anbima_tpf_data(date: DateScalar, bond_type: str | None = None) -> pd.DataFrame:
|
|
112
110
|
"""Fetch and process TPF market data from ANBIMA.
|
|
113
111
|
|
|
114
112
|
This function retrieves bond market data from the ANBIMA website for a
|
|
@@ -116,7 +114,7 @@ def anbima_tpf_data(
|
|
|
116
114
|
and attempts to download the data from both member and non-member URLs.
|
|
117
115
|
|
|
118
116
|
Args:
|
|
119
|
-
|
|
117
|
+
date (DateScalar): The reference date for the data.
|
|
120
118
|
bond_type (str, optional): Filter data by bond type.
|
|
121
119
|
Defaults to None, which returns data for all bond types.
|
|
122
120
|
|
|
@@ -126,8 +124,8 @@ def anbima_tpf_data(
|
|
|
126
124
|
specified date.
|
|
127
125
|
"""
|
|
128
126
|
# Normalize the reference date
|
|
129
|
-
|
|
130
|
-
file_content = _get_file_content(
|
|
127
|
+
date = dc.convert_input_dates(date)
|
|
128
|
+
file_content = _get_file_content(date)
|
|
131
129
|
|
|
132
130
|
if not file_content:
|
|
133
131
|
return pd.DataFrame()
|
|
@@ -144,7 +142,7 @@ def anbima_tpf_data(
|
|
|
144
142
|
|
|
145
143
|
|
|
146
144
|
def anbima_tpf_rates(
|
|
147
|
-
|
|
145
|
+
date: DateScalar,
|
|
148
146
|
bond_type: str | None = None,
|
|
149
147
|
adj_maturities: bool = False,
|
|
150
148
|
) -> pd.DataFrame:
|
|
@@ -156,7 +154,7 @@ def anbima_tpf_rates(
|
|
|
156
154
|
website using the :func:`data` function.
|
|
157
155
|
|
|
158
156
|
Args:
|
|
159
|
-
|
|
157
|
+
date (DateScalar): The reference date for the rates.
|
|
160
158
|
bond_type (str, optional): Filter rates by bond type.
|
|
161
159
|
Defaults to None, which returns rates for all bond types.
|
|
162
160
|
adj_maturities (bool, optional): Adjust maturity dates to the next
|
|
@@ -169,12 +167,12 @@ def anbima_tpf_rates(
|
|
|
169
167
|
"""
|
|
170
168
|
df = get_anbima_dataset()
|
|
171
169
|
|
|
172
|
-
|
|
173
|
-
df.query("ReferenceDate == @
|
|
170
|
+
date = dc.convert_input_dates(date)
|
|
171
|
+
df.query("ReferenceDate == @date", inplace=True)
|
|
174
172
|
|
|
175
173
|
if df.empty:
|
|
176
174
|
# Try to fetch the data from the Anbima website
|
|
177
|
-
df = anbima_tpf_data(
|
|
175
|
+
df = anbima_tpf_data(date)
|
|
178
176
|
|
|
179
177
|
if df.empty:
|
|
180
178
|
# If the data is still empty, return an empty DataFrame
|
|
@@ -190,7 +188,7 @@ def anbima_tpf_rates(
|
|
|
190
188
|
return df.sort_values(["BondType", "MaturityDate"], ignore_index=True)
|
|
191
189
|
|
|
192
190
|
|
|
193
|
-
def tpf_pre_maturities(
|
|
191
|
+
def tpf_pre_maturities(date: DateScalar) -> pd.Series:
|
|
194
192
|
"""Retrieve pre-defined maturity dates for LTN and NTN-F bonds.
|
|
195
193
|
|
|
196
194
|
This function fetches pre-defined maturity dates for 'LTN' (prefixadas) and
|
|
@@ -198,14 +196,14 @@ def tpf_pre_maturities(reference_date: DateScalar) -> pd.Series:
|
|
|
198
196
|
for a given reference date.
|
|
199
197
|
|
|
200
198
|
Args:
|
|
201
|
-
|
|
199
|
+
date (DateScalar): The reference date for maturity dates.
|
|
202
200
|
|
|
203
201
|
Returns:
|
|
204
202
|
pd.Series: A Series containing unique maturity dates for 'LTN' and
|
|
205
203
|
'NTN-F' bonds, sorted in ascending order.
|
|
206
204
|
"""
|
|
207
205
|
maturity_dates = (
|
|
208
|
-
anbima_tpf_rates(
|
|
206
|
+
anbima_tpf_rates(date)
|
|
209
207
|
.query("BondType in ['LTN', 'NTN-F']")["MaturityDate"]
|
|
210
208
|
.drop_duplicates()
|
|
211
209
|
.sort_values(ignore_index=True)
|
|
@@ -15,7 +15,7 @@ ContractOptions = Literal["DI1", "DDI", "FRC", "DAP", "DOL", "WDO", "IND", "WIN"
|
|
|
15
15
|
|
|
16
16
|
def futures(
|
|
17
17
|
contract_code: ContractOptions,
|
|
18
|
-
|
|
18
|
+
date: DateScalar,
|
|
19
19
|
) -> pd.DataFrame:
|
|
20
20
|
"""
|
|
21
21
|
Fetches data for a specified futures contract based on type and reference date.
|
|
@@ -31,7 +31,7 @@ def futures(
|
|
|
31
31
|
- "WDO": Mini U.S. Dollar Futures from B3.
|
|
32
32
|
- "IND": Ibovespa Futures from B3.
|
|
33
33
|
- "WIN": Mini Ibovespa Futures from B3.
|
|
34
|
-
|
|
34
|
+
date (DateScalar): The date for which to fetch the data.
|
|
35
35
|
If the reference date is a string, it should be in 'DD-MM-YYYY' format.
|
|
36
36
|
|
|
37
37
|
Returns:
|
|
@@ -58,14 +58,14 @@ def futures(
|
|
|
58
58
|
...
|
|
59
59
|
"""
|
|
60
60
|
selected_contract = str(contract_code).upper()
|
|
61
|
-
|
|
61
|
+
converted_date = dc.convert_input_dates(date)
|
|
62
62
|
|
|
63
63
|
# First, try to fetch historical data for the specified date
|
|
64
|
-
df = fetch_historical_df(selected_contract,
|
|
64
|
+
df = fetch_historical_df(selected_contract, converted_date)
|
|
65
65
|
|
|
66
66
|
bz_today = dt.datetime.now(TIMEZONE_BZ).date()
|
|
67
67
|
# If there is no historical data available, try to fetch intraday data
|
|
68
|
-
if
|
|
68
|
+
if converted_date.date() == bz_today and df.empty:
|
|
69
69
|
df = fetch_intraday_df(selected_contract)
|
|
70
70
|
|
|
71
71
|
return df
|
|
@@ -17,7 +17,7 @@ class DIFutures:
|
|
|
17
17
|
NTN-F bond maturities.
|
|
18
18
|
|
|
19
19
|
Args:
|
|
20
|
-
|
|
20
|
+
date (DateScalar | None): The trade date to retrieve the
|
|
21
21
|
DI contract data. If None, the latest available historical trade date is
|
|
22
22
|
used.
|
|
23
23
|
month_start (bool): If True, adjusts the expirations to the start of the month.
|
|
@@ -28,7 +28,7 @@ class DIFutures:
|
|
|
28
28
|
|
|
29
29
|
Examples:
|
|
30
30
|
To create a `DIFutures` instance and retrieve data:
|
|
31
|
-
>>> dif = yd.DIFutures(
|
|
31
|
+
>>> dif = yd.DIFutures(date="16-10-2024", month_start=True)
|
|
32
32
|
>>> df = dif.df # Retrieve DI contract dataframe for the specified date
|
|
33
33
|
>>> df.iloc[:5, :5] # Show the first five rows and columns
|
|
34
34
|
TradeDate ExpirationDate TickerSymbol BDaysToExp OpenContracts
|
|
@@ -48,7 +48,7 @@ class DIFutures:
|
|
|
48
48
|
4 2024-10-16 2025-03-01 0.1157 0.12343
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
historical_dates = (
|
|
52
52
|
get_di_dataset()
|
|
53
53
|
.drop_duplicates(subset=["TradeDate"])["TradeDate"]
|
|
54
54
|
.sort_values(ascending=True)
|
|
@@ -62,7 +62,7 @@ class DIFutures:
|
|
|
62
62
|
|
|
63
63
|
def __init__(
|
|
64
64
|
self,
|
|
65
|
-
|
|
65
|
+
date: DateScalar | None = None,
|
|
66
66
|
month_start: bool = False,
|
|
67
67
|
pre_filter: bool = False,
|
|
68
68
|
all_columns: bool = True,
|
|
@@ -77,7 +77,7 @@ class DIFutures:
|
|
|
77
77
|
self._df = pd.DataFrame()
|
|
78
78
|
self._dirty = True # Attribute to track if the df needs updating
|
|
79
79
|
|
|
80
|
-
self.
|
|
80
|
+
self.date = date
|
|
81
81
|
self.month_start = month_start
|
|
82
82
|
self.pre_filter = pre_filter
|
|
83
83
|
self.all_columns = all_columns
|
|
@@ -102,14 +102,14 @@ class DIFutures:
|
|
|
102
102
|
def _update_df(self) -> pd.DataFrame:
|
|
103
103
|
"""Retrieve DI contract DataFrame for the initialized trade date."""
|
|
104
104
|
bz_today = pd.Timestamp.today(tz="America/Sao_Paulo").normalize()
|
|
105
|
-
if bz_today == self.
|
|
105
|
+
if bz_today == self.date:
|
|
106
106
|
# Try to get intraday data
|
|
107
|
-
df = b3.futures(contract_code="DI1",
|
|
107
|
+
df = b3.futures(contract_code="DI1", date=bz_today)
|
|
108
108
|
else:
|
|
109
109
|
# Get historical data
|
|
110
110
|
df = (
|
|
111
111
|
get_di_dataset()
|
|
112
|
-
.query("TradeDate == @self.
|
|
112
|
+
.query("TradeDate == @self._date")
|
|
113
113
|
.reset_index(drop=True)
|
|
114
114
|
)
|
|
115
115
|
|
|
@@ -123,7 +123,7 @@ class DIFutures:
|
|
|
123
123
|
df_pre = (
|
|
124
124
|
get_anbima_dataset()
|
|
125
125
|
.query("BondType in ['LTN', 'NTN-F']")
|
|
126
|
-
.query("ReferenceDate == @self.
|
|
126
|
+
.query("ReferenceDate == @self._date")[
|
|
127
127
|
["ReferenceDate", "MaturityDate"]
|
|
128
128
|
]
|
|
129
129
|
.drop_duplicates()
|
|
@@ -197,7 +197,7 @@ class DIFutures:
|
|
|
197
197
|
|
|
198
198
|
@staticmethod
|
|
199
199
|
def interpolate_rates(
|
|
200
|
-
|
|
200
|
+
dates: DateScalar | DateArray,
|
|
201
201
|
expirations: DateScalar | DateArray,
|
|
202
202
|
extrapolate: bool = True,
|
|
203
203
|
) -> pd.Series:
|
|
@@ -209,12 +209,12 @@ class DIFutures:
|
|
|
209
209
|
dates and maturities using a flat-forward interpolation method. If no DI
|
|
210
210
|
rates are available for a reference date, the interpolated rate is set to NaN.
|
|
211
211
|
|
|
212
|
-
If
|
|
212
|
+
If dates is provided as a scalar and expirations as an array, the
|
|
213
213
|
method assumes the scalar value is the same for all maturities. The same logic
|
|
214
214
|
applies when the maturities are scalar and the trade dates are an array.
|
|
215
215
|
|
|
216
216
|
Args:
|
|
217
|
-
|
|
217
|
+
dates (DateScalar | DateArray): The trade dates for the rates.
|
|
218
218
|
expirations (DateScalar | DateArray): The expirations corresponding to the
|
|
219
219
|
trade dates.
|
|
220
220
|
extrapolate (bool): Whether to allow extrapolation beyond known DI rates.
|
|
@@ -223,29 +223,29 @@ class DIFutures:
|
|
|
223
223
|
pd.Series: A Series containing the interpolated DI rates.
|
|
224
224
|
|
|
225
225
|
Raises:
|
|
226
|
-
ValueError: If `
|
|
226
|
+
ValueError: If `dates` and `maturities` have different lengths.
|
|
227
227
|
"""
|
|
228
228
|
# Convert input dates to a consistent format
|
|
229
|
-
|
|
229
|
+
dates = dc.convert_input_dates(dates)
|
|
230
230
|
expirations = dc.convert_input_dates(expirations)
|
|
231
231
|
|
|
232
232
|
# Ensure the lengths of input arrays are consistent
|
|
233
|
-
match (
|
|
233
|
+
match (dates, expirations):
|
|
234
234
|
case pd.Timestamp(), pd.Series():
|
|
235
235
|
dfi = pd.DataFrame({"mat": expirations})
|
|
236
|
-
dfi["tdate"] =
|
|
236
|
+
dfi["tdate"] = dates
|
|
237
237
|
|
|
238
238
|
case pd.Series(), pd.Timestamp():
|
|
239
|
-
dfi = pd.DataFrame({"tdate":
|
|
239
|
+
dfi = pd.DataFrame({"tdate": dates})
|
|
240
240
|
dfi["mat"] = expirations
|
|
241
241
|
|
|
242
242
|
case pd.Series(), pd.Series():
|
|
243
|
-
if len(
|
|
243
|
+
if len(dates) != len(expirations):
|
|
244
244
|
raise ValueError("Args. should have the same length.")
|
|
245
|
-
dfi = pd.DataFrame({"tdate":
|
|
245
|
+
dfi = pd.DataFrame({"tdate": dates, "mat": expirations})
|
|
246
246
|
|
|
247
247
|
case pd.Timestamp(), pd.Timestamp():
|
|
248
|
-
dfi = pd.DataFrame({"tdate": [
|
|
248
|
+
dfi = pd.DataFrame({"tdate": [dates], "mat": [expirations]})
|
|
249
249
|
|
|
250
250
|
# Compute business days between reference dates and maturities
|
|
251
251
|
dfi["bdays"] = bday.count(dfi["tdate"], dfi["mat"])
|
|
@@ -332,11 +332,11 @@ class DIFutures:
|
|
|
332
332
|
known_rates=df["SettlementRate"],
|
|
333
333
|
extrapolate=extrapolate,
|
|
334
334
|
)
|
|
335
|
-
bd = bday.count(self.
|
|
335
|
+
bd = bday.count(self._date, expiration)
|
|
336
336
|
return ff_interp(bd)
|
|
337
337
|
|
|
338
338
|
@property
|
|
339
|
-
def
|
|
339
|
+
def date(self) -> pd.Timestamp:
|
|
340
340
|
"""
|
|
341
341
|
The trade date to retrieve the DI contract data.
|
|
342
342
|
|
|
@@ -347,14 +347,14 @@ class DIFutures:
|
|
|
347
347
|
pd.Timestamp: The trade date set for this instance.
|
|
348
348
|
"""
|
|
349
349
|
|
|
350
|
-
return self.
|
|
350
|
+
return self._date
|
|
351
351
|
|
|
352
|
-
@
|
|
353
|
-
def
|
|
352
|
+
@date.setter
|
|
353
|
+
def date(self, value: DateScalar | None):
|
|
354
354
|
if value:
|
|
355
|
-
self.
|
|
355
|
+
self._date = dc.convert_input_dates(value)
|
|
356
356
|
else:
|
|
357
|
-
self.
|
|
357
|
+
self._date = self.historical_dates.max()
|
|
358
358
|
|
|
359
359
|
self._dirty = True
|
|
360
360
|
|
|
@@ -18,9 +18,7 @@ COUNT_CONVENTIONS = {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def get_old_expiration_date(
|
|
22
|
-
expiration_code: str, trade_date: pd.Timestamp
|
|
23
|
-
) -> pd.Timestamp:
|
|
21
|
+
def get_old_expiration_date(expiration_code: str, date: pd.Timestamp) -> pd.Timestamp:
|
|
24
22
|
"""
|
|
25
23
|
Internal function to convert an old DI contract code into its ExpirationDate date.
|
|
26
24
|
Valid for contract codes up to 21-05-2006.
|
|
@@ -29,7 +27,7 @@ def get_old_expiration_date(
|
|
|
29
27
|
expiration_code (str): An old DI Expiration Code from B3, where the first three
|
|
30
28
|
letters represent the month and the last digit represents the year.
|
|
31
29
|
Example: "JAN3".
|
|
32
|
-
|
|
30
|
+
date (pd.Timestamp): The trade date for which the contract code is valid.
|
|
33
31
|
|
|
34
32
|
Returns:
|
|
35
33
|
pd.Timestamp
|
|
@@ -64,7 +62,7 @@ def get_old_expiration_date(
|
|
|
64
62
|
month = month_codes[month_code]
|
|
65
63
|
|
|
66
64
|
# Year codes must generated dynamically, since it depends on the trade date.
|
|
67
|
-
reference_year =
|
|
65
|
+
reference_year = date.year
|
|
68
66
|
year_codes = {}
|
|
69
67
|
for year in range(reference_year, reference_year + 10):
|
|
70
68
|
year_codes[str(year)[-1:]] = year
|
|
@@ -105,18 +103,18 @@ def _convert_prices_to_rates(
|
|
|
105
103
|
return rates.round(5)
|
|
106
104
|
|
|
107
105
|
|
|
108
|
-
def _fetch_raw_df(contract_code: str,
|
|
106
|
+
def _fetch_raw_df(contract_code: str, date: pd.Timestamp) -> pd.DataFrame:
|
|
109
107
|
"""
|
|
110
108
|
Fetch the historical futures data from B3 for a specific trade date. If the data is
|
|
111
109
|
not available, an empty DataFrame is returned.
|
|
112
110
|
|
|
113
111
|
Args:
|
|
114
|
-
|
|
112
|
+
date (pd.Timestamp): The trade date for which the data should be fetched.
|
|
115
113
|
|
|
116
114
|
Returns:
|
|
117
115
|
pd.DataFrame: Raw DI data as a Pandas pd.DataFrame.
|
|
118
116
|
"""
|
|
119
|
-
url_date =
|
|
117
|
+
url_date = date.strftime("%d/%m/%Y")
|
|
120
118
|
# url example: https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data=05/10/2023&Mercadoria=DI1
|
|
121
119
|
url = f"https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data={url_date}&Mercadoria={contract_code}&XLS=false"
|
|
122
120
|
r = requests.get(url, timeout=10)
|
|
@@ -185,19 +183,19 @@ def _rename_columns(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
185
183
|
|
|
186
184
|
|
|
187
185
|
def process_df(
|
|
188
|
-
input_df: pd.DataFrame,
|
|
186
|
+
input_df: pd.DataFrame, date: pd.Timestamp, asset_code: str
|
|
189
187
|
) -> pd.DataFrame:
|
|
190
188
|
df = input_df.copy()
|
|
191
|
-
df["TradeDate"] =
|
|
189
|
+
df["TradeDate"] = date
|
|
192
190
|
# Convert to datetime64[ns] since it is pandas default type for timestamps
|
|
193
191
|
df["TradeDate"] = df["TradeDate"].astype("datetime64[ns]")
|
|
194
192
|
|
|
195
193
|
df["TickerSymbol"] = asset_code + df["ExpirationCode"]
|
|
196
194
|
|
|
197
195
|
# Contract code format was changed in 22/05/2006
|
|
198
|
-
if
|
|
196
|
+
if date < pd.Timestamp("2006-05-22"):
|
|
199
197
|
df["ExpirationDate"] = df["ExpirationCode"].apply(
|
|
200
|
-
get_old_expiration_date, args=(
|
|
198
|
+
get_old_expiration_date, args=(date,)
|
|
201
199
|
)
|
|
202
200
|
else:
|
|
203
201
|
expiration_day = 15 if asset_code == "DAP" else 1
|
|
@@ -205,11 +203,11 @@ def process_df(
|
|
|
205
203
|
common.get_expiration_date, args=(expiration_day,)
|
|
206
204
|
)
|
|
207
205
|
|
|
208
|
-
df["DaysToExp"] = (df["ExpirationDate"] -
|
|
206
|
+
df["DaysToExp"] = (df["ExpirationDate"] - date).dt.days
|
|
209
207
|
# Convert to nullable integer, since it is the default type in the library
|
|
210
208
|
df["DaysToExp"] = df["DaysToExp"].astype("Int64")
|
|
211
209
|
|
|
212
|
-
df["BDaysToExp"] = bday.count(
|
|
210
|
+
df["BDaysToExp"] = bday.count(date, df["ExpirationDate"])
|
|
213
211
|
|
|
214
212
|
# Remove expired contracts
|
|
215
213
|
df.query("DaysToExp > 0", inplace=True)
|
|
@@ -223,7 +221,7 @@ def process_df(
|
|
|
223
221
|
|
|
224
222
|
rate_cols = [col for col in df.columns if "Rate" in col]
|
|
225
223
|
# Prior to 17/01/2002 (inclusive), DI prices were not converted to rates
|
|
226
|
-
if
|
|
224
|
+
if date <= pd.Timestamp("2002-01-17") and asset_code == "DI1":
|
|
227
225
|
df = _adjust_older_contracts_rates(df, rate_cols)
|
|
228
226
|
else:
|
|
229
227
|
# Remove % and round to 5 (3 in %) dec. places in rate columns
|
|
@@ -281,7 +279,7 @@ def _select_and_reorder_columns(df: pd.DataFrame):
|
|
|
281
279
|
return df[reordered_columns]
|
|
282
280
|
|
|
283
281
|
|
|
284
|
-
def fetch_historical_df(contract_code: str,
|
|
282
|
+
def fetch_historical_df(contract_code: str, date: pd.Timestamp) -> pd.DataFrame:
|
|
285
283
|
"""
|
|
286
284
|
Fetchs the futures data for a given date from B3.
|
|
287
285
|
|
|
@@ -290,7 +288,7 @@ def fetch_historical_df(contract_code: str, trade_date: pd.Timestamp) -> pd.Data
|
|
|
290
288
|
|
|
291
289
|
Args:
|
|
292
290
|
asset_code (str): The asset code to fetch the futures data.
|
|
293
|
-
|
|
291
|
+
date (pd.Timestamp): The trade date to fetch the futures data.
|
|
294
292
|
count_convention (int): The count convention for the DI futures contract.
|
|
295
293
|
Can be 252 business days or 360 calendar days.
|
|
296
294
|
|
|
@@ -298,11 +296,11 @@ def fetch_historical_df(contract_code: str, trade_date: pd.Timestamp) -> pd.Data
|
|
|
298
296
|
pd.DataFrame: A Pandas pd.DataFrame containing processed futures data. If
|
|
299
297
|
the data is not available, an empty DataFrame is returned.
|
|
300
298
|
"""
|
|
301
|
-
df_raw = _fetch_raw_df(contract_code,
|
|
299
|
+
df_raw = _fetch_raw_df(contract_code, date)
|
|
302
300
|
if df_raw.empty:
|
|
303
301
|
return df_raw
|
|
304
302
|
|
|
305
303
|
df = _rename_columns(df_raw)
|
|
306
|
-
df = process_df(df,
|
|
304
|
+
df = process_df(df, date, contract_code)
|
|
307
305
|
df = _select_and_reorder_columns(df)
|
|
308
306
|
return df
|
|
@@ -22,7 +22,7 @@ def _get_file_from_path(file_path: Path) -> io.BytesIO:
|
|
|
22
22
|
return io.BytesIO(content)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def _get_file_from_url(
|
|
25
|
+
def _get_file_from_url(date: pd.Timestamp, source_type: str) -> io.BytesIO:
|
|
26
26
|
"""
|
|
27
27
|
Types of XML files available:
|
|
28
28
|
Full Price Report (all assets)
|
|
@@ -33,12 +33,14 @@ def _get_file_from_url(trade_date: pd.Timestamp, source_type: str) -> io.BytesIO
|
|
|
33
33
|
url example: https://www.b3.com.br/pesquisapregao/download?filelist=SPRD240216.zip
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
date_str = date.strftime("%y%m%d")
|
|
37
37
|
|
|
38
38
|
if source_type == "PR":
|
|
39
|
-
url = f"https://www.b3.com.br/pesquisapregao/download?filelist=PR{
|
|
39
|
+
url = f"https://www.b3.com.br/pesquisapregao/download?filelist=PR{date_str}.zip"
|
|
40
40
|
elif source_type == "SPR":
|
|
41
|
-
url =
|
|
41
|
+
url = (
|
|
42
|
+
f"https://www.b3.com.br/pesquisapregao/download?filelist=SPRD{date_str}.zip"
|
|
43
|
+
)
|
|
42
44
|
else:
|
|
43
45
|
raise ValueError("Invalid source type. Must be either 'PR' or 'SPR'.")
|
|
44
46
|
|
|
@@ -46,8 +48,8 @@ def _get_file_from_url(trade_date: pd.Timestamp, source_type: str) -> io.BytesIO
|
|
|
46
48
|
|
|
47
49
|
# File will be considered invalid if it is too small
|
|
48
50
|
if response.status_code != 200 or len(response.content) < 1024:
|
|
49
|
-
|
|
50
|
-
raise ValueError(f"There is no data available for {
|
|
51
|
+
date_str = date.strftime("%Y-%m-%d")
|
|
52
|
+
raise ValueError(f"There is no data available for {date_str}.")
|
|
51
53
|
|
|
52
54
|
return io.BytesIO(response.content)
|
|
53
55
|
|
|
@@ -112,12 +114,12 @@ def _extract_data_from_xml(xml_file: io.BytesIO, asset_code: str) -> list[dict]:
|
|
|
112
114
|
continue
|
|
113
115
|
|
|
114
116
|
# Extract the trade date
|
|
115
|
-
|
|
116
|
-
if
|
|
117
|
+
date = price_report.find(".//ns:TradDt/ns:Dt", namespaces)
|
|
118
|
+
if date is None:
|
|
117
119
|
continue
|
|
118
120
|
|
|
119
121
|
# Store the data in a dictionary
|
|
120
|
-
ticker_data = {"TradDt":
|
|
122
|
+
ticker_data = {"TradDt": date.text, "TckrSymb": ticker.text}
|
|
121
123
|
|
|
122
124
|
# Extract the FinInstrmAttrbts element
|
|
123
125
|
fin_instrm_attrbts = price_report.find(".//ns:FinInstrmAttrbts", namespaces)
|
|
@@ -260,9 +262,9 @@ def process_zip_file(zip_file: io.BytesIO, asset_code: str) -> pd.DataFrame:
|
|
|
260
262
|
|
|
261
263
|
|
|
262
264
|
def fetch_df(
|
|
263
|
-
|
|
265
|
+
date: pd.Timestamp, asset_code: str, source_type: Literal["PR", "SPR"]
|
|
264
266
|
) -> pd.DataFrame:
|
|
265
|
-
zip_file = _get_file_from_url(
|
|
267
|
+
zip_file = _get_file_from_url(date, source_type)
|
|
266
268
|
df = process_zip_file(zip_file, asset_code)
|
|
267
269
|
return df
|
|
268
270
|
|
|
@@ -19,7 +19,7 @@ MAX_ATTEMPTS = 10
|
|
|
19
19
|
|
|
20
20
|
def indicator(
|
|
21
21
|
indicator_code: Literal["IPCA_MR", "SELIC_TARGET", "SELIC_OVER", "DI", "VNA_LFT"],
|
|
22
|
-
|
|
22
|
+
date: DateScalar,
|
|
23
23
|
) -> float:
|
|
24
24
|
"""
|
|
25
25
|
Fetches the economic indicator value for a specified reference date.
|
|
@@ -37,7 +37,7 @@ def indicator(
|
|
|
37
37
|
- "SELIC_OVER": SELIC Over (overnight) rate.
|
|
38
38
|
- "DI": DI (interbank deposit rate).
|
|
39
39
|
- "VNA_LFT": Valor Nominal Atualizado for LFT (Treasury Bills).
|
|
40
|
-
|
|
40
|
+
date (DateScalar): The date for which the indicator value is
|
|
41
41
|
fetched. If passed as a string, it should be in 'DD-MM-YYYY' format.
|
|
42
42
|
|
|
43
43
|
Returns:
|
|
@@ -65,7 +65,7 @@ def indicator(
|
|
|
65
65
|
0.104
|
|
66
66
|
|
|
67
67
|
"""
|
|
68
|
-
converted_date = dc.convert_input_dates(
|
|
68
|
+
converted_date = dc.convert_input_dates(date)
|
|
69
69
|
selected_indicator_code = str(indicator_code).upper()
|
|
70
70
|
match selected_indicator_code:
|
|
71
71
|
case "IPCA_MR":
|
|
@@ -82,7 +82,7 @@ def indicator(
|
|
|
82
82
|
raise ValueError("Invalid indicator code provided")
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
def ipca_monthly_rate(
|
|
85
|
+
def ipca_monthly_rate(date: pd.Timestamp) -> float:
|
|
86
86
|
"""
|
|
87
87
|
The function makes an API call to the IBGE's data portal to retrieve the data.
|
|
88
88
|
An example of the API call: https://servicodados.ibge.gov.br/api/v3/agregados/6691/periodos/202403/variaveis/63?localidades=N1[all]
|
|
@@ -90,14 +90,14 @@ def ipca_monthly_rate(reference_date: pd.Timestamp) -> float:
|
|
|
90
90
|
The API URL is constructed dynamically based on the reference date provided.
|
|
91
91
|
"""
|
|
92
92
|
# Format the date as 'YYYYMM' for the API endpoint
|
|
93
|
-
ipca_date =
|
|
93
|
+
ipca_date = date.strftime("%Y%m")
|
|
94
94
|
|
|
95
95
|
# Construct the API URL using the formatted date
|
|
96
96
|
api_url = f"https://servicodados.ibge.gov.br/api/v3/agregados/6691/periodos/{ipca_date}/variaveis/63?localidades=N1[all]"
|
|
97
97
|
|
|
98
98
|
def process_ipca_response(data):
|
|
99
99
|
if not data:
|
|
100
|
-
msg = f"No data available for IPCA Monthly Rate on {
|
|
100
|
+
msg = f"No data available for IPCA Monthly Rate on {date}"
|
|
101
101
|
logger.warning(msg)
|
|
102
102
|
return float("nan")
|
|
103
103
|
ipca_str = data[0]["resultados"][0]["series"][0]["serie"][ipca_date]
|
|
@@ -106,28 +106,28 @@ def ipca_monthly_rate(reference_date: pd.Timestamp) -> float:
|
|
|
106
106
|
return _fetch_with_retry(api_url, "IPCA Monthly Rate", process_ipca_response, True)
|
|
107
107
|
|
|
108
108
|
|
|
109
|
-
def _selic_target(
|
|
109
|
+
def _selic_target(date: pd.Timestamp) -> float:
|
|
110
110
|
"""
|
|
111
111
|
Fetches the SELIC Target rate for a specific reference date.
|
|
112
112
|
|
|
113
113
|
The SELIC Target rate is the official rate set by the Central Bank of Brazil.
|
|
114
114
|
|
|
115
115
|
Args:
|
|
116
|
-
|
|
116
|
+
date (pd.Timestamp): The date for which to fetch the SELIC Target.
|
|
117
117
|
|
|
118
118
|
Returns:
|
|
119
119
|
float: The SELIC Target rate as a float rounded to 4 decimal places or NaN if
|
|
120
120
|
the rate is not available.
|
|
121
121
|
"""
|
|
122
122
|
# https://api.bcb.gov.br/dados/serie/bcdata.sgs.432/dados?formato=json&dataInicial=12/04/2024&dataFinal=12/04/2024
|
|
123
|
-
selic_date =
|
|
123
|
+
selic_date = date.strftime("%d/%m/%Y")
|
|
124
124
|
api_url = f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.432/dados?formato=csv&dataInicial={selic_date}&dataFinal={selic_date}"
|
|
125
125
|
|
|
126
126
|
def process_selic_target_response(data):
|
|
127
127
|
csv_data = io.StringIO(data)
|
|
128
128
|
df = pd.read_csv(csv_data, sep=";", decimal=",")
|
|
129
129
|
if df.empty or "valor" not in df.columns:
|
|
130
|
-
msg = f"No data available for SELIC Target rate on {
|
|
130
|
+
msg = f"No data available for SELIC Target rate on {date}"
|
|
131
131
|
logger.warning(msg)
|
|
132
132
|
return float("nan")
|
|
133
133
|
value = float(df["valor"].iloc[0] / 100) # SELIC Target rate
|
|
@@ -138,7 +138,7 @@ def _selic_target(reference_date: pd.Timestamp) -> float:
|
|
|
138
138
|
)
|
|
139
139
|
|
|
140
140
|
|
|
141
|
-
def _selic_over(
|
|
141
|
+
def _selic_over(date: pd.Timestamp) -> float:
|
|
142
142
|
"""
|
|
143
143
|
Fetches the SELIC Over rate for a specific reference date.
|
|
144
144
|
|
|
@@ -146,14 +146,14 @@ def _selic_over(reference_date: pd.Timestamp) -> float:
|
|
|
146
146
|
between banks in the interbank market, using public securities as collateral.
|
|
147
147
|
|
|
148
148
|
Args:
|
|
149
|
-
|
|
149
|
+
date (pd.Timestamp): The date for which to fetch the SELIC Over rate.
|
|
150
150
|
|
|
151
151
|
Returns:
|
|
152
152
|
float: The SELIC Over rate as a float rounded to 4 decimal places or NaN if
|
|
153
153
|
the rate is not available.
|
|
154
154
|
"""
|
|
155
155
|
|
|
156
|
-
formatted_date =
|
|
156
|
+
formatted_date = date.strftime("%d/%m/%Y")
|
|
157
157
|
# https://api.bcb.gov.br/dados/serie/bcdata.sgs.1178/dados?formato=json&dataInicial=12/04/2024&dataFinal=12/04/2024
|
|
158
158
|
api_url = f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.1178/dados?formato=csv&dataInicial={formatted_date}&dataFinal={formatted_date}"
|
|
159
159
|
|
|
@@ -161,7 +161,7 @@ def _selic_over(reference_date: pd.Timestamp) -> float:
|
|
|
161
161
|
csv_data = io.StringIO(data)
|
|
162
162
|
df = pd.read_csv(csv_data, sep=";", decimal=",")
|
|
163
163
|
if df.empty or "valor" not in df.columns:
|
|
164
|
-
msg = f"No data available for SELIC Over rate on {
|
|
164
|
+
msg = f"No data available for SELIC Over rate on {date}"
|
|
165
165
|
logger.warning(msg)
|
|
166
166
|
return float("nan")
|
|
167
167
|
value = float(df["valor"].iloc[0] / 100) # SELIC Over daily rate
|
|
@@ -170,16 +170,16 @@ def _selic_over(reference_date: pd.Timestamp) -> float:
|
|
|
170
170
|
return _fetch_with_retry(api_url, "SELIC Over", process_selic_over_response, False)
|
|
171
171
|
|
|
172
172
|
|
|
173
|
-
def _di(
|
|
173
|
+
def _di(date: pd.Timestamp) -> float:
|
|
174
174
|
# https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial=12/04/2024&dataFinal=12/04/2024
|
|
175
|
-
di_date =
|
|
175
|
+
di_date = date.strftime("%d/%m/%Y")
|
|
176
176
|
api_url = f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=csv&dataInicial={di_date}&dataFinal={di_date}"
|
|
177
177
|
|
|
178
178
|
def process_di_response(data):
|
|
179
179
|
csv_data = io.StringIO(data)
|
|
180
180
|
df = pd.read_csv(csv_data, sep=";", decimal=",")
|
|
181
181
|
if df.empty or "valor" not in df.columns:
|
|
182
|
-
logger.warning(f"No data available for DI rate on {
|
|
182
|
+
logger.warning(f"No data available for DI rate on {date}")
|
|
183
183
|
return float("nan")
|
|
184
184
|
value = float(df["valor"].iloc[0] / 100) # DI daily rate
|
|
185
185
|
return round((1 + value) ** 252 - 1, 4) # Annualize the daily rate
|
|
@@ -187,10 +187,10 @@ def _di(reference_date: pd.Timestamp) -> float:
|
|
|
187
187
|
return _fetch_with_retry(api_url, "DI Over", process_di_response, False)
|
|
188
188
|
|
|
189
189
|
|
|
190
|
-
def _vna_lft(
|
|
190
|
+
def _vna_lft(date: pd.Timestamp) -> float:
|
|
191
191
|
# url example: https://www3.bcb.gov.br/novoselic/rest/arquivosDiarios/pub/download/3/20240418APC238
|
|
192
192
|
url_base = "https://www3.bcb.gov.br/novoselic/rest/arquivosDiarios/pub/download/3/"
|
|
193
|
-
url_file = f"{
|
|
193
|
+
url_file = f"{date.strftime('%Y%m%d')}APC238"
|
|
194
194
|
url_vna = url_base + url_file
|
|
195
195
|
|
|
196
196
|
def _process_vna_response(text: str) -> float:
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import io
|
|
2
1
|
import locale
|
|
3
2
|
from dataclasses import dataclass
|
|
4
3
|
from typing import Literal
|
|
5
4
|
|
|
6
5
|
import pandas as pd
|
|
7
|
-
import python_calamine as pc
|
|
8
|
-
import requests
|
|
9
6
|
|
|
10
7
|
|
|
11
8
|
@dataclass
|
|
@@ -62,32 +59,23 @@ def ipca_current_month() -> IndicatorProjection:
|
|
|
62
59
|
"""
|
|
63
60
|
# Define the URL and get the data
|
|
64
61
|
url = "https://www.anbima.com.br/informacoes/indicadores/arqs/indicadores.xls"
|
|
65
|
-
|
|
66
|
-
excel_data = io.BytesIO(response.content)
|
|
62
|
+
df = pd.read_excel(url)
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
workbook = pc.load_workbook(excel_data)
|
|
70
|
-
first_sheet = workbook.sheet_names[0]
|
|
71
|
-
data = workbook.get_sheet_by_name(first_sheet).to_python(skip_empty_area=True)
|
|
64
|
+
last_update_str = df.columns[0].split("Atualização:")[-1].strip()
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
# Find the text containing the IPCA projection and extract its data
|
|
78
|
-
ipca_data = next(line for line in data if "IPCA1" in line)
|
|
79
|
-
ipca_text = str(ipca_data[-1])
|
|
66
|
+
ipca_row = df[df.iloc[:, 0] == "IPCA1"]
|
|
67
|
+
ipca_date = ipca_row.iloc[0, 1]
|
|
68
|
+
ipca_value = ipca_row.iloc[0, 2]
|
|
80
69
|
|
|
81
70
|
# Extract and format the reference month
|
|
82
|
-
|
|
83
|
-
month_str = projection_text.split("(")[-1].split(")")[0]
|
|
71
|
+
ipca_date = ipca_date.split("(")[-1].split(")")[0]
|
|
84
72
|
locale.setlocale(locale.LC_TIME, "pt_BR.UTF-8")
|
|
85
|
-
|
|
86
|
-
reference_period =
|
|
73
|
+
ipca_date = pd.to_datetime(ipca_date, format="%b/%y")
|
|
74
|
+
reference_period = ipca_date.to_period("M")
|
|
87
75
|
locale.setlocale(locale.LC_TIME, "") # Reset locale to default
|
|
88
76
|
|
|
89
77
|
return IndicatorProjection(
|
|
90
78
|
last_updated=pd.to_datetime(last_update_str, format="%d/%m/%Y - %H:%M h"),
|
|
91
79
|
reference_period=reference_period,
|
|
92
|
-
projected_value=round(float(
|
|
80
|
+
projected_value=round(float(ipca_value) / 100, 4),
|
|
93
81
|
)
|
|
@@ -7,33 +7,33 @@ from pyield.date_converter import DateScalar
|
|
|
7
7
|
from pyield.tpf import tools as tt
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def rates(
|
|
10
|
+
def rates(date: DateScalar) -> pd.DataFrame:
|
|
11
11
|
"""
|
|
12
12
|
Fetch the bond Anbima indicative rates for the given reference date.
|
|
13
13
|
|
|
14
14
|
Args:
|
|
15
|
-
|
|
15
|
+
date (DateScalar): The reference date for fetching the data.
|
|
16
16
|
|
|
17
17
|
Returns:
|
|
18
18
|
pd.DataFrame: DataFrame with columns "MaturityDate" and "IndicativeRate".
|
|
19
19
|
"""
|
|
20
|
-
lft_rates = tpf.anbima_tpf_rates(
|
|
20
|
+
lft_rates = tpf.anbima_tpf_rates(date, "LFT")
|
|
21
21
|
if lft_rates.empty:
|
|
22
22
|
return pd.DataFrame()
|
|
23
23
|
return lft_rates[["MaturityDate", "IndicativeRate"]]
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def maturities(
|
|
26
|
+
def maturities(date: DateScalar) -> pd.Series:
|
|
27
27
|
"""
|
|
28
28
|
Fetch the bond maturities available for the given reference date.
|
|
29
29
|
|
|
30
30
|
Args:
|
|
31
|
-
|
|
31
|
+
date (DateScalar): The reference date for fetching the data.
|
|
32
32
|
|
|
33
33
|
Returns:
|
|
34
34
|
pd.Series: A Series of bond maturities available for the reference date.
|
|
35
35
|
"""
|
|
36
|
-
df_rates = rates(
|
|
36
|
+
df_rates = rates(date)
|
|
37
37
|
return df_rates["MaturityDate"]
|
|
38
38
|
|
|
39
39
|
|
|
@@ -10,12 +10,12 @@ from pyield.tpf import tools as tt
|
|
|
10
10
|
FACE_VALUE = 1000
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def rates(
|
|
13
|
+
def rates(date: DateScalar) -> pd.DataFrame:
|
|
14
14
|
"""
|
|
15
15
|
Fetch the LTN Anbima indicative rates for the given reference date.
|
|
16
16
|
|
|
17
17
|
Args:
|
|
18
|
-
|
|
18
|
+
date (DateScalar): The reference date for fetching the data.
|
|
19
19
|
|
|
20
20
|
Returns:
|
|
21
21
|
pd.DataFrame: DataFrame with columns "MaturityDate" and "IndicativeRate".
|
|
@@ -38,18 +38,18 @@ def rates(reference_date: DateScalar) -> pd.DataFrame:
|
|
|
38
38
|
12 2030-01-01 0.118746
|
|
39
39
|
|
|
40
40
|
"""
|
|
41
|
-
ltn_rates = tpf.anbima_tpf_rates(
|
|
41
|
+
ltn_rates = tpf.anbima_tpf_rates(date, "LTN")
|
|
42
42
|
if ltn_rates.empty:
|
|
43
43
|
return pd.DataFrame()
|
|
44
44
|
return ltn_rates[["MaturityDate", "IndicativeRate"]]
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def maturities(
|
|
47
|
+
def maturities(date: DateScalar) -> pd.Series:
|
|
48
48
|
"""
|
|
49
49
|
Fetch the bond maturities available for the given reference date.
|
|
50
50
|
|
|
51
51
|
Args:
|
|
52
|
-
|
|
52
|
+
date (DateScalar): The reference date for fetching the data.
|
|
53
53
|
|
|
54
54
|
Returns:
|
|
55
55
|
pd.Series: A Series of bond maturities available for the reference date.
|
|
@@ -72,7 +72,7 @@ def maturities(reference_date: DateScalar) -> pd.Series:
|
|
|
72
72
|
dtype: datetime64[ns]
|
|
73
73
|
|
|
74
74
|
"""
|
|
75
|
-
df_rates = rates(
|
|
75
|
+
df_rates = rates(date)
|
|
76
76
|
s_maturities = df_rates["MaturityDate"]
|
|
77
77
|
s_maturities.name = None
|
|
78
78
|
return s_maturities
|
|
@@ -121,7 +121,7 @@ def price(
|
|
|
121
121
|
return tt.truncate(FACE_VALUE / discount_factor, 6)
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
def di_spreads(
|
|
124
|
+
def di_spreads(date: DateScalar) -> pd.DataFrame:
|
|
125
125
|
"""
|
|
126
126
|
Calculates the DI spread for the LTN based on ANBIMA's indicative rates.
|
|
127
127
|
|
|
@@ -129,7 +129,7 @@ def di_spreads(reference_date: DateScalar) -> pd.DataFrame:
|
|
|
129
129
|
rates and calculates the spread between these rates in basis points.
|
|
130
130
|
|
|
131
131
|
Parameters:
|
|
132
|
-
|
|
132
|
+
date (DateScalar, optional): The reference date for the
|
|
133
133
|
spread calculation.
|
|
134
134
|
|
|
135
135
|
Returns:
|
|
@@ -153,7 +153,7 @@ def di_spreads(reference_date: DateScalar) -> pd.DataFrame:
|
|
|
153
153
|
12 2030-01-01 14.96
|
|
154
154
|
"""
|
|
155
155
|
# Fetch DI Spreads for the reference date
|
|
156
|
-
df = tt.pre_spreads(
|
|
156
|
+
df = tt.pre_spreads(date)
|
|
157
157
|
df.query("BondType == 'LTN'", inplace=True)
|
|
158
158
|
df.sort_values(["MaturityDate"], ignore_index=True, inplace=True)
|
|
159
159
|
return df[["MaturityDate", "DISpread"]]
|
|
@@ -186,14 +186,14 @@ def premium(ltn_rate: float, di_rate: float) -> float:
|
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
def historical_premium(
|
|
189
|
-
|
|
189
|
+
date: DateScalar,
|
|
190
190
|
maturity: DateScalar,
|
|
191
191
|
) -> float:
|
|
192
192
|
"""
|
|
193
193
|
Calculate the premium of the LTN bond over the DI Future rate for a given date.
|
|
194
194
|
|
|
195
195
|
Args:
|
|
196
|
-
|
|
196
|
+
date (DateScalar): The reference date to fetch the rates.
|
|
197
197
|
maturity (DateScalar): The maturity date of the LTN bond.
|
|
198
198
|
|
|
199
199
|
Returns:
|
|
@@ -206,11 +206,11 @@ def historical_premium(
|
|
|
206
206
|
|
|
207
207
|
"""
|
|
208
208
|
# Convert input dates to a consistent format
|
|
209
|
-
|
|
209
|
+
date = dc.convert_input_dates(date)
|
|
210
210
|
maturity = dc.convert_input_dates(maturity)
|
|
211
211
|
|
|
212
212
|
# Retrieve LTN rates for the reference date
|
|
213
|
-
df_anbima = rates(
|
|
213
|
+
df_anbima = rates(date)
|
|
214
214
|
if df_anbima.empty:
|
|
215
215
|
return float("NaN")
|
|
216
216
|
|
|
@@ -221,7 +221,7 @@ def historical_premium(
|
|
|
221
221
|
ltn_rate = float(ltn_rates.iloc[0])
|
|
222
222
|
|
|
223
223
|
# Retrieve DI rate for the reference date and maturity
|
|
224
|
-
dif = di.DIFutures(
|
|
224
|
+
dif = di.DIFutures(date=date)
|
|
225
225
|
di_rate = dif.rate(expiration=maturity, interpolate=True, extrapolate=False)
|
|
226
226
|
if pd.isnull(di_rate): # Check if the DI rate is NaN
|
|
227
227
|
return float("NaN")
|
|
@@ -53,12 +53,12 @@ def _check_maturities(
|
|
|
53
53
|
raise ValueError("NTN-B maturity must be 15/02, 15/05, 15/08 or 15/11.")
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
def rates(
|
|
56
|
+
def rates(date: DateScalar) -> pd.DataFrame:
|
|
57
57
|
"""
|
|
58
58
|
Fetch the bond indicative rates for the given reference date.
|
|
59
59
|
|
|
60
60
|
Args:
|
|
61
|
-
|
|
61
|
+
date (DateScalar): The reference date for fetching the data.
|
|
62
62
|
|
|
63
63
|
Returns:
|
|
64
64
|
pd.DataFrame: DataFrame with columns "MaturityDate" and "IndicativeRate".
|
|
@@ -86,18 +86,18 @@ def rates(reference_date: DateScalar) -> pd.DataFrame:
|
|
|
86
86
|
13 2060-08-15 0.060179
|
|
87
87
|
|
|
88
88
|
"""
|
|
89
|
-
ntnb_rates = tpf.anbima_tpf_rates(
|
|
89
|
+
ntnb_rates = tpf.anbima_tpf_rates(date, "NTN-B")
|
|
90
90
|
if ntnb_rates.empty:
|
|
91
91
|
return pd.DataFrame()
|
|
92
92
|
return ntnb_rates[["MaturityDate", "IndicativeRate"]]
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
def maturities(
|
|
95
|
+
def maturities(date: DateScalar) -> pd.Series:
|
|
96
96
|
"""
|
|
97
97
|
Get the bond maturities available for the given reference date.
|
|
98
98
|
|
|
99
99
|
Args:
|
|
100
|
-
|
|
100
|
+
date (DateScalar): The reference date for fetching the data.
|
|
101
101
|
|
|
102
102
|
Returns:
|
|
103
103
|
pd.Series: Series containing the maturity dates for the NTN-B bonds.
|
|
@@ -122,7 +122,7 @@ def maturities(reference_date: DateScalar) -> pd.Series:
|
|
|
122
122
|
dtype: datetime64[ns]
|
|
123
123
|
|
|
124
124
|
"""
|
|
125
|
-
df_rates = rates(
|
|
125
|
+
df_rates = rates(date)
|
|
126
126
|
s_maturities = df_rates["MaturityDate"]
|
|
127
127
|
s_maturities.name = None
|
|
128
128
|
return s_maturities
|
|
@@ -3,7 +3,7 @@ from collections.abc import Callable
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pandas as pd
|
|
5
5
|
|
|
6
|
-
from pyield import bday
|
|
6
|
+
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
|
|
@@ -24,23 +24,12 @@ COUPON_PMT = 48.80885
|
|
|
24
24
|
FINAL_PMT = 1048.80885
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def
|
|
28
|
-
"""
|
|
29
|
-
Fetch the NTN-F bond reference dates available in the Anbima database.
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
pd.Series: A Series of NTN-F bond reference dates.
|
|
33
|
-
"""
|
|
34
|
-
df = data_cache.get_anbima_dataset().query("BondType == 'NTN-F'")
|
|
35
|
-
return df["ReferenceDate"].drop_duplicates().sort_values().reset_index(drop=True)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def rates(reference_date: DateScalar) -> pd.DataFrame:
|
|
27
|
+
def rates(date: DateScalar) -> pd.DataFrame:
|
|
39
28
|
"""
|
|
40
29
|
Fetch the bond indicative rates for the given reference date.
|
|
41
30
|
|
|
42
31
|
Args:
|
|
43
|
-
|
|
32
|
+
date (DateScalar): The reference date for fetching the data.
|
|
44
33
|
|
|
45
34
|
Returns:
|
|
46
35
|
pd.DataFrame: DataFrame with columns "MaturityDate" and "IndicativeRate".
|
|
@@ -55,18 +44,18 @@ def rates(reference_date: DateScalar) -> pd.DataFrame:
|
|
|
55
44
|
4 2033-01-01 0.116307
|
|
56
45
|
5 2035-01-01 0.116586
|
|
57
46
|
"""
|
|
58
|
-
ntnf_rates = tpf.anbima_tpf_rates(
|
|
47
|
+
ntnf_rates = tpf.anbima_tpf_rates(date, "NTN-F")
|
|
59
48
|
if ntnf_rates.empty:
|
|
60
49
|
return pd.DataFrame()
|
|
61
50
|
return ntnf_rates[["MaturityDate", "IndicativeRate"]]
|
|
62
51
|
|
|
63
52
|
|
|
64
|
-
def maturities(
|
|
53
|
+
def maturities(date: DateScalar) -> pd.Series:
|
|
65
54
|
"""
|
|
66
55
|
Fetch the NTN-F bond maturities available for the given reference date.
|
|
67
56
|
|
|
68
57
|
Args:
|
|
69
|
-
|
|
58
|
+
date (DateScalar): The reference date for fetching the data.
|
|
70
59
|
|
|
71
60
|
Returns:
|
|
72
61
|
pd.Series: A Series of NTN-F bond maturities available for the reference date.
|
|
@@ -82,7 +71,7 @@ def maturities(reference_date: DateScalar) -> pd.Series:
|
|
|
82
71
|
dtype: datetime64[ns]
|
|
83
72
|
|
|
84
73
|
"""
|
|
85
|
-
df_rates = rates(
|
|
74
|
+
df_rates = rates(date)
|
|
86
75
|
s_maturities = df_rates["MaturityDate"]
|
|
87
76
|
s_maturities.name = None
|
|
88
77
|
return s_maturities
|
|
@@ -341,7 +330,7 @@ def spot_rates( # noqa
|
|
|
341
330
|
return df
|
|
342
331
|
|
|
343
332
|
|
|
344
|
-
def di_spreads(
|
|
333
|
+
def di_spreads(date: DateScalar) -> pd.DataFrame:
|
|
345
334
|
"""
|
|
346
335
|
Calculates the DI spread for the NTN-F based on ANBIMA's indicative rates.
|
|
347
336
|
|
|
@@ -349,7 +338,7 @@ def di_spreads(reference_date: DateScalar) -> pd.DataFrame:
|
|
|
349
338
|
rates and calculates the spread between these rates in basis points.
|
|
350
339
|
|
|
351
340
|
Parameters:
|
|
352
|
-
|
|
341
|
+
date (DateScalar): The reference date for the spread calculation.
|
|
353
342
|
|
|
354
343
|
Returns:
|
|
355
344
|
pd.DataFrame: DataFrame with columns "MaturityDate", "DISpread".
|
|
@@ -366,7 +355,7 @@ def di_spreads(reference_date: DateScalar) -> pd.DataFrame:
|
|
|
366
355
|
|
|
367
356
|
"""
|
|
368
357
|
# Fetch DI Spreads for the reference date
|
|
369
|
-
df = tt.pre_spreads(
|
|
358
|
+
df = tt.pre_spreads(date)
|
|
370
359
|
df = (
|
|
371
360
|
df.query("BondType == 'NTN-F'")
|
|
372
361
|
.sort_values(["MaturityDate"])
|
|
@@ -587,13 +576,13 @@ def premium(
|
|
|
587
576
|
|
|
588
577
|
|
|
589
578
|
def historical_premium(
|
|
590
|
-
|
|
579
|
+
date: DateScalar,
|
|
591
580
|
maturity: DateScalar,
|
|
592
581
|
) -> float:
|
|
593
|
-
|
|
582
|
+
date = dc.convert_input_dates(date)
|
|
594
583
|
maturity = dc.convert_input_dates(maturity)
|
|
595
584
|
|
|
596
|
-
df_ntnf = rates(
|
|
585
|
+
df_ntnf = rates(date)
|
|
597
586
|
if df_ntnf.empty:
|
|
598
587
|
return float("NaN")
|
|
599
588
|
|
|
@@ -602,14 +591,14 @@ def historical_premium(
|
|
|
602
591
|
return float("NaN")
|
|
603
592
|
ntnf_ytm = float(ntnf_ytms.iloc[0])
|
|
604
593
|
|
|
605
|
-
df = cash_flows(
|
|
606
|
-
df["BDToMat"] = bday.count(
|
|
594
|
+
df = cash_flows(date, maturity, adj_payment_dates=True)
|
|
595
|
+
df["BDToMat"] = bday.count(date, df["PaymentDate"])
|
|
607
596
|
df["BYears"] = df["BDToMat"] / 252
|
|
608
|
-
df["ReferenceDate"] =
|
|
597
|
+
df["ReferenceDate"] = date
|
|
609
598
|
|
|
610
599
|
dif = di.DIFutures() # Instantiate the DI Futures class
|
|
611
600
|
df["DIRate"] = dif.interpolate_rates(
|
|
612
|
-
|
|
601
|
+
dates=df["ReferenceDate"],
|
|
613
602
|
expirations=df["PaymentDate"],
|
|
614
603
|
extrapolate=False,
|
|
615
604
|
)
|
|
@@ -58,7 +58,7 @@ def calculate_present_value(
|
|
|
58
58
|
return (cash_flows / (1 + rates) ** periods).sum()
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
def pre_spreads(
|
|
61
|
+
def pre_spreads(date: DateScalar) -> pd.DataFrame:
|
|
62
62
|
"""
|
|
63
63
|
Calculates the DI spread for Brazilian treasury bonds (LTN and NTN-F) based on
|
|
64
64
|
ANBIMA's indicative rates.
|
|
@@ -69,15 +69,15 @@ def pre_spreads(reference_date: DateScalar) -> pd.DataFrame:
|
|
|
69
69
|
provided, the function uses the previous business day.
|
|
70
70
|
|
|
71
71
|
Parameters:
|
|
72
|
-
|
|
72
|
+
date (DateScalar): The reference date for the spread calculation.
|
|
73
73
|
|
|
74
74
|
Returns:
|
|
75
75
|
pd.DataFrame: DataFrame containing the bond type, maturity date and the
|
|
76
76
|
calculated spread in basis points.
|
|
77
77
|
"""
|
|
78
78
|
# Fetch DI rates for the reference date
|
|
79
|
-
converted_date = dc.convert_input_dates(
|
|
80
|
-
di = DIFutures(
|
|
79
|
+
converted_date = dc.convert_input_dates(date)
|
|
80
|
+
di = DIFutures(date=converted_date, month_start=True)
|
|
81
81
|
df_di = di.df
|
|
82
82
|
if "SettlementRate" not in df_di.columns:
|
|
83
83
|
raise ValueError("DI rates data is missing the 'SettlementRate' column.")
|
|
@@ -14,9 +14,8 @@ classifiers = [
|
|
|
14
14
|
"Topic :: Office/Business :: Financial :: Investment",
|
|
15
15
|
]
|
|
16
16
|
dependencies = [
|
|
17
|
-
"pandas[html,parquet]>=2.0.0",
|
|
18
|
-
"numpy>=2.0.0",
|
|
19
|
-
"python-calamine>=0.2.0",
|
|
17
|
+
"pandas[html,parquet,excel]>=2.0.0",
|
|
18
|
+
"numpy>=2.0.0",
|
|
20
19
|
"requests>=2.31.0",
|
|
21
20
|
]
|
|
22
21
|
dynamic = ["version"]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.28.0"
|
|
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
|