PYield 0.7.0__tar.gz → 0.7.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.7.0 → pyield-0.7.2}/PKG-INFO +10 -10
- {pyield-0.7.0 → pyield-0.7.2}/README.md +9 -9
- pyield-0.7.2/pyield/__about__.py +1 -0
- {pyield-0.7.0 → pyield-0.7.2}/pyield/data_access.py +11 -10
- {pyield-0.7.0 → pyield-0.7.2}/pyield/data_analysis.py +3 -1
- pyield-0.7.2/pyield/futures/__init__.py +10 -0
- pyield-0.7.2/pyield/futures/common.py +175 -0
- pyield-0.7.2/pyield/futures/ddi.py +178 -0
- pyield-0.7.0/pyield/di/web.py → pyield-0.7.2/pyield/futures/di.py +16 -130
- pyield-0.7.0/pyield/di/xml.py → pyield-0.7.2/pyield/futures/di_xml.py +47 -7
- {pyield-0.7.0 → pyield-0.7.2}/pyield/treasuries.py +4 -4
- {pyield-0.7.0 → pyield-0.7.2}/pyproject.toml +1 -1
- pyield-0.7.0/tests/test_di.py → pyield-0.7.2/tests/test_futures.py +11 -11
- pyield-0.7.0/pyield/__about__.py +0 -1
- pyield-0.7.0/pyield/di/__init__.py +0 -6
- pyield-0.7.0/pyield/di/core.py +0 -180
- {pyield-0.7.0 → pyield-0.7.2}/LICENSE +0 -0
- {pyield-0.7.0 → pyield-0.7.2}/pyield/__init__.py +0 -0
- {pyield-0.7.0 → pyield-0.7.2}/pyield/bday.py +0 -0
- {pyield-0.7.0 → pyield-0.7.2}/pyield/holidays/__init__.py +0 -0
- {pyield-0.7.0 → pyield-0.7.2}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.7.0 → pyield-0.7.2}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.7.0 → pyield-0.7.2}/pyield/holidays/core.py +0 -0
- {pyield-0.7.0 → pyield-0.7.2}/pyield/indicators.py +0 -0
- {pyield-0.7.0 → pyield-0.7.2}/pyield/py.typed +0 -0
- {pyield-0.7.0 → pyield-0.7.2}/pyield/utils.py +0 -0
- {pyield-0.7.0 → pyield-0.7.2}/tests/__init__.py +0 -0
- pyield-0.7.0/tests/test_br_calendar.py → pyield-0.7.2/tests/test_bday.py +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PYield
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.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>
|
|
@@ -48,7 +48,7 @@ Description-Content-Type: text/markdown
|
|
|
48
48
|
|
|
49
49
|
## Introduction
|
|
50
50
|
|
|
51
|
-
Welcome to PYield, a Python library designed for the analysis of fixed income instruments in Brazil. This library is tailored for financial analysts, researchers, and enthusiasts interested in the Brazilian fixed income market. Leveraging the power of popular Python libraries like Pandas and Requests, PYield simplifies the process of obtaining and processing data from key sources such as ANBIMA and B3.
|
|
51
|
+
Welcome to PYield, a Python library designed for the analysis of fixed income instruments in Brazil. This library is tailored for financial analysts, researchers, and enthusiasts interested in the Brazilian fixed income market. Leveraging the power of popular Python libraries like Pandas and Requests, PYield simplifies the process of obtaining and processing data from key sources such as ANBIMA, BCB, IBGE and B3.
|
|
52
52
|
|
|
53
53
|
## Features
|
|
54
54
|
|
|
@@ -101,14 +101,14 @@ dtype: datetime64[ns]
|
|
|
101
101
|
# Fetch a DataFrame with the DI Futures data from B3
|
|
102
102
|
>>> yd.fetch_asset(asset_code="DI1", reference_date='2024-03-08')
|
|
103
103
|
|
|
104
|
-
TradeDate ExpirationCode ExpirationDate
|
|
105
|
-
2024-03-08 J24 2024-04-01 15
|
|
106
|
-
2024-03-08 K24 2024-05-02 37
|
|
107
|
-
2024-03-08 M24 2024-06-03 58
|
|
108
|
-
... ... ... ...
|
|
109
|
-
2024-03-08 F37 2037-01-02 3213
|
|
110
|
-
2024-03-08 F38 2038-01-04 3462
|
|
111
|
-
2024-03-08 F39 2039-01-03 3713
|
|
104
|
+
TradeDate ExpirationCode ExpirationDate BDaysToExpiration ... LastRate LastAskRate LastBidRate SettlementRate
|
|
105
|
+
2024-03-08 J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
|
|
106
|
+
2024-03-08 K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
|
|
107
|
+
2024-03-08 M24 2024-06-03 58 ... 10.604 10.602 10.604 10.608
|
|
108
|
+
... ... ... ... ... ... ... ... ...
|
|
109
|
+
2024-03-08 F37 2037-01-02 3213 ... <NA> <NA> <NA> 10.859
|
|
110
|
+
2024-03-08 F38 2038-01-04 3462 ... <NA> <NA> <NA> 10.859
|
|
111
|
+
2024-03-08 F39 2039-01-03 3713 ... <NA> <NA> <NA> 10.85
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
### Treasury Bonds Data
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## Introduction
|
|
8
8
|
|
|
9
|
-
Welcome to PYield, a Python library designed for the analysis of fixed income instruments in Brazil. This library is tailored for financial analysts, researchers, and enthusiasts interested in the Brazilian fixed income market. Leveraging the power of popular Python libraries like Pandas and Requests, PYield simplifies the process of obtaining and processing data from key sources such as ANBIMA and B3.
|
|
9
|
+
Welcome to PYield, a Python library designed for the analysis of fixed income instruments in Brazil. This library is tailored for financial analysts, researchers, and enthusiasts interested in the Brazilian fixed income market. Leveraging the power of popular Python libraries like Pandas and Requests, PYield simplifies the process of obtaining and processing data from key sources such as ANBIMA, BCB, IBGE and B3.
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
@@ -59,14 +59,14 @@ dtype: datetime64[ns]
|
|
|
59
59
|
# Fetch a DataFrame with the DI Futures data from B3
|
|
60
60
|
>>> yd.fetch_asset(asset_code="DI1", reference_date='2024-03-08')
|
|
61
61
|
|
|
62
|
-
TradeDate ExpirationCode ExpirationDate
|
|
63
|
-
2024-03-08 J24 2024-04-01 15
|
|
64
|
-
2024-03-08 K24 2024-05-02 37
|
|
65
|
-
2024-03-08 M24 2024-06-03 58
|
|
66
|
-
... ... ... ...
|
|
67
|
-
2024-03-08 F37 2037-01-02 3213
|
|
68
|
-
2024-03-08 F38 2038-01-04 3462
|
|
69
|
-
2024-03-08 F39 2039-01-03 3713
|
|
62
|
+
TradeDate ExpirationCode ExpirationDate BDaysToExpiration ... LastRate LastAskRate LastBidRate SettlementRate
|
|
63
|
+
2024-03-08 J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
|
|
64
|
+
2024-03-08 K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
|
|
65
|
+
2024-03-08 M24 2024-06-03 58 ... 10.604 10.602 10.604 10.608
|
|
66
|
+
... ... ... ... ... ... ... ... ...
|
|
67
|
+
2024-03-08 F37 2037-01-02 3213 ... <NA> <NA> <NA> 10.859
|
|
68
|
+
2024-03-08 F38 2038-01-04 3462 ... <NA> <NA> <NA> 10.859
|
|
69
|
+
2024-03-08 F39 2039-01-03 3713 ... <NA> <NA> <NA> 10.85
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
### Treasury Bonds Data
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.2"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
|
|
3
|
-
from . import
|
|
4
|
-
from . import indicators as
|
|
3
|
+
from . import futures as ft
|
|
4
|
+
from . import indicators as it
|
|
5
5
|
from . import treasuries as tr
|
|
6
6
|
from .utils import _normalize_date
|
|
7
7
|
|
|
@@ -20,7 +20,8 @@ def fetch_asset(
|
|
|
20
20
|
- "TRB": Treasury bonds (indicative rates from ANBIMA).
|
|
21
21
|
- "LTN", "LFT", "NTN-F", "NTN-B": Specific types of Brazilian treasury bonds
|
|
22
22
|
(indicative rates from ANBIMA).
|
|
23
|
-
- "DI1":
|
|
23
|
+
- "DI1": One-day Interbank Deposit Futures (Futuro de DI) from B3.
|
|
24
|
+
- "DDI": DI x U.S. Dollar Spread Futures (Futuro de Cupom Cambial) from B3.
|
|
24
25
|
reference_date (str | pd.Timestamp | None): The reference date for which data is
|
|
25
26
|
fetched. Defaults to the previous business day if None.
|
|
26
27
|
**kwargs: Additional keyword arguments, specifically:
|
|
@@ -41,15 +42,15 @@ def fetch_asset(
|
|
|
41
42
|
normalized_date = _normalize_date(reference_date)
|
|
42
43
|
|
|
43
44
|
if asset_code.lower() == "trb":
|
|
44
|
-
return tr.
|
|
45
|
+
return tr.fetch_bonds(reference_date=normalized_date, return_raw=return_raw)
|
|
45
46
|
elif asset_code.lower() in ["ltn", "lft", "ntn-f", "ntn-b"]:
|
|
46
|
-
df = tr.
|
|
47
|
+
df = tr.fetch_bonds(reference_date=normalized_date)
|
|
47
48
|
return df.query(f"BondType == '{asset_code.upper()}'")
|
|
48
49
|
|
|
49
50
|
elif asset_code.lower() == "di1":
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
)
|
|
51
|
+
return ft.fetch_di(trade_date=normalized_date, return_raw=return_raw)
|
|
52
|
+
elif asset_code.lower() == "ddi":
|
|
53
|
+
return ft.fetch_ddi(trade_date=normalized_date, return_raw=return_raw)
|
|
53
54
|
else:
|
|
54
55
|
raise ValueError("Asset type not supported.")
|
|
55
56
|
|
|
@@ -81,8 +82,8 @@ def fetch_indicator(
|
|
|
81
82
|
normalized_date = _normalize_date(reference_date)
|
|
82
83
|
|
|
83
84
|
if indicator_code.lower() == "selic":
|
|
84
|
-
return
|
|
85
|
+
return it.fetch_selic_target(reference_date=normalized_date)
|
|
85
86
|
elif indicator_code.lower() == "ipca":
|
|
86
|
-
return
|
|
87
|
+
return it.fetch_ipca_mr(reference_date=normalized_date)
|
|
87
88
|
else:
|
|
88
89
|
raise ValueError("Indicator type not supported.")
|
|
@@ -4,7 +4,9 @@ from . import treasuries as tr
|
|
|
4
4
|
from .utils import _normalize_date
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def calculate_spreads(
|
|
7
|
+
def calculate_spreads(
|
|
8
|
+
spread_type: str, reference_date: str | pd.Timestamp | None = None
|
|
9
|
+
) -> pd.DataFrame:
|
|
8
10
|
"""
|
|
9
11
|
Calculates spreads between assets based on the specified spread type.
|
|
10
12
|
If no reference date is provided, the function uses the previous business day.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import io
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from .. import bday
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_expiration_date(expiration_code: str) -> pd.Timestamp:
|
|
10
|
+
"""
|
|
11
|
+
Converts an expiration code into its corresponding expiration date.
|
|
12
|
+
|
|
13
|
+
This function translates an expiration code into a specific expiration date based on
|
|
14
|
+
a given mapping. The expiration code consists of a letter representing the month and
|
|
15
|
+
two digits for the year. The function ensures the date returned is a valid business
|
|
16
|
+
day by adjusting weekends and holidays as necessary.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
expiration_code (str): The expiration code to be converted, where the first
|
|
20
|
+
letter represents the month and the last two digits represent the year
|
|
21
|
+
(e.g., "F23" for January 2023).
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
pd.Timestamp: The expiration date corresponding to the code, adjusted to a valid
|
|
25
|
+
business day. Returns pd.NaT if the code is invalid.
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
>>> get_expiration_date("F23")
|
|
29
|
+
pd.Timestamp('2023-01-01')
|
|
30
|
+
|
|
31
|
+
>>> get_expiration_date("Z33")
|
|
32
|
+
pd.Timestamp('2033-12-01')
|
|
33
|
+
|
|
34
|
+
>>> get_expiration_date("A99")
|
|
35
|
+
pd.NaT
|
|
36
|
+
|
|
37
|
+
Notes:
|
|
38
|
+
The expiration date is calculated based on the format change introduced by B3 on
|
|
39
|
+
22-05-2006, where the first letter represents the month and the last two digits
|
|
40
|
+
represent the year.
|
|
41
|
+
"""
|
|
42
|
+
month_codes = {
|
|
43
|
+
"F": 1,
|
|
44
|
+
"G": 2,
|
|
45
|
+
"H": 3,
|
|
46
|
+
"J": 4,
|
|
47
|
+
"K": 5,
|
|
48
|
+
"M": 6,
|
|
49
|
+
"N": 7,
|
|
50
|
+
"Q": 8,
|
|
51
|
+
"U": 9,
|
|
52
|
+
"V": 10,
|
|
53
|
+
"X": 11,
|
|
54
|
+
"Z": 12,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
month_code = expiration_code[0]
|
|
59
|
+
month = month_codes[month_code]
|
|
60
|
+
year = int("20" + expiration_code[-2:])
|
|
61
|
+
# The expiration date is always the first business day of the month
|
|
62
|
+
expiration = pd.Timestamp(year, month, 1)
|
|
63
|
+
|
|
64
|
+
# Adjust to the next business day when expiration date is a weekend or a holiday
|
|
65
|
+
adj_expiration = bday.offset_bdays(expiration, offset=0)
|
|
66
|
+
|
|
67
|
+
return adj_expiration
|
|
68
|
+
|
|
69
|
+
except (KeyError, ValueError):
|
|
70
|
+
return pd.NaT # type: ignore
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_old_expiration_date(
|
|
74
|
+
expiration_code: str, trade_date: pd.Timestamp
|
|
75
|
+
) -> pd.Timestamp:
|
|
76
|
+
"""
|
|
77
|
+
Internal function to convert an old DI contract code into its ExpirationDate date.
|
|
78
|
+
Valid for contract codes up to 21-05-2006.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
expiration_code (str): An old DI Expiration Code from B3, where the first three
|
|
82
|
+
letters represent the month and the last digit represents the year.
|
|
83
|
+
Example: "JAN3".
|
|
84
|
+
trade_date (pd.Timestamp): The trade date for which the contract code is valid.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
pd.Timestamp
|
|
88
|
+
The contract's ExpirationDate date. Returns pd.NaT if the input is invalid.
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
>>> get_old_expiration_date("JAN3", pd.Timestamp("2001-05-21"))
|
|
92
|
+
pd.Timestamp('2003-01-01')
|
|
93
|
+
|
|
94
|
+
Notes:
|
|
95
|
+
- In 22-05-2006, B3 changed the format of the DI contract codes. Before that
|
|
96
|
+
date, the first three letters represented the month and the last digit
|
|
97
|
+
represented the year.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
month_codes = {
|
|
101
|
+
"JAN": 1,
|
|
102
|
+
"FEV": 2,
|
|
103
|
+
"MAR": 3,
|
|
104
|
+
"ABR": 4,
|
|
105
|
+
"MAI": 5,
|
|
106
|
+
"JUN": 6,
|
|
107
|
+
"JUL": 7,
|
|
108
|
+
"AGO": 8,
|
|
109
|
+
"SET": 9,
|
|
110
|
+
"OUT": 10,
|
|
111
|
+
"NOV": 11,
|
|
112
|
+
"DEZ": 12,
|
|
113
|
+
}
|
|
114
|
+
try:
|
|
115
|
+
month_code = expiration_code[:3]
|
|
116
|
+
month = month_codes[month_code]
|
|
117
|
+
|
|
118
|
+
# Year codes must generated dynamically, since it depends on the trade date.
|
|
119
|
+
reference_year = trade_date.year
|
|
120
|
+
year_codes = {}
|
|
121
|
+
for year in range(reference_year, reference_year + 10):
|
|
122
|
+
year_codes[str(year)[-1:]] = year
|
|
123
|
+
year = year_codes[expiration_code[-1:]]
|
|
124
|
+
|
|
125
|
+
expiration_date = pd.Timestamp(year, month, 1)
|
|
126
|
+
# Adjust to the next business day when the date is a weekend or a holiday.
|
|
127
|
+
# Must use old holiday list, since this contract code was used until 2006.
|
|
128
|
+
return bday.offset_bdays(expiration_date, offset=0, holiday_list="old")
|
|
129
|
+
|
|
130
|
+
except (KeyError, ValueError):
|
|
131
|
+
return pd.NaT # type: ignore
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _fetch_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
135
|
+
"""
|
|
136
|
+
Internal function to fetch raw DI futures data from B3 for a specific trade date.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
trade_date: a datetime-like object representing the trade date.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
pd.DataFrame: Raw DI data as a Pandas pd.DataFrame.
|
|
143
|
+
"""
|
|
144
|
+
url_date = trade_date.strftime("%d/%m/%Y")
|
|
145
|
+
# url example: https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data=05/10/2023&Mercadoria=DI1
|
|
146
|
+
url = f"https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data={url_date}&Mercadoria={asset_code}&XLS=false"
|
|
147
|
+
r = requests.get(url)
|
|
148
|
+
|
|
149
|
+
text = r.text
|
|
150
|
+
if "AJUSTE" not in text:
|
|
151
|
+
raise ValueError(f"Could not fetch data for {url_date}.")
|
|
152
|
+
|
|
153
|
+
df = pd.read_html(
|
|
154
|
+
io.StringIO(text),
|
|
155
|
+
match="AJUSTE",
|
|
156
|
+
header=1,
|
|
157
|
+
thousands=".",
|
|
158
|
+
decimal=",",
|
|
159
|
+
na_values=["-"],
|
|
160
|
+
dtype_backend="numpy_nullable",
|
|
161
|
+
)[0]
|
|
162
|
+
|
|
163
|
+
# Remove rows with all NaN values
|
|
164
|
+
df = df.dropna(how="all")
|
|
165
|
+
|
|
166
|
+
# Remove columns with all NaN values
|
|
167
|
+
df = df.dropna(axis=1, how="all")
|
|
168
|
+
|
|
169
|
+
# Force "VAR. PTOS." to be string, since it can also be read as float
|
|
170
|
+
df["VAR. PTOS."] = df["VAR. PTOS."].astype(pd.StringDtype())
|
|
171
|
+
|
|
172
|
+
# Force "AJUSTE CORRIG. (4)" to be float, since it can be also read as int
|
|
173
|
+
df["AJUSTE CORRIG. (4)"] = df["AJUSTE CORRIG. (4)"].astype(pd.Float64Dtype())
|
|
174
|
+
|
|
175
|
+
return df
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from . import common
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _convert_prices_to_rates(prices: pd.Series, n_days: pd.Series) -> pd.Series:
|
|
7
|
+
"""
|
|
8
|
+
Internal function to convert DDI futures prices to rates.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
prices (pd.Series): A pd.Series containing DDI futures prices.
|
|
12
|
+
bd (pd.Series): A serie containing the number of days to expiration.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
pd.Series: A pd.Series containing DDI futures rates.
|
|
16
|
+
"""
|
|
17
|
+
rates = (100_000 / prices - 1) * (360 / n_days)
|
|
18
|
+
|
|
19
|
+
# Return rates as percentage
|
|
20
|
+
return 100 * rates
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _convert_prices_in_older_contracts(df: pd.DataFrame) -> pd.DataFrame:
|
|
24
|
+
# Prior to 01/01/2002, prices were not converted to rates
|
|
25
|
+
convert_cols = [
|
|
26
|
+
"FirstRate",
|
|
27
|
+
"MinRate",
|
|
28
|
+
"MaxRate",
|
|
29
|
+
"AvgRate",
|
|
30
|
+
"LastRate",
|
|
31
|
+
"LastBidRate",
|
|
32
|
+
"LastAskRate",
|
|
33
|
+
]
|
|
34
|
+
for col in convert_cols:
|
|
35
|
+
df[col] = _convert_prices_to_rates(df[col], df["DaysToExpiration"])
|
|
36
|
+
|
|
37
|
+
# Invert low and high prices
|
|
38
|
+
df["MinRate"], df["MaxRate"] = df["MaxRate"], df["MinRate"]
|
|
39
|
+
|
|
40
|
+
return df
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
44
|
+
"""
|
|
45
|
+
Internal function to process and transform raw DI futures data.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
df (pd.DataFrame): the raw DI DataFrame.
|
|
49
|
+
trade_date: a datetime-like object representing the trade date.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
pd.DataFrame: Processed and transformed data as a Pandas pd.DataFrame.
|
|
53
|
+
"""
|
|
54
|
+
# Check if the pd.DataFrame is empty
|
|
55
|
+
if df.empty:
|
|
56
|
+
return df
|
|
57
|
+
|
|
58
|
+
rename_dict = {
|
|
59
|
+
"VENCTO": "ExpirationCode",
|
|
60
|
+
"CONTR. ABERT.(1)": "OpenContracts", # At the start of the day
|
|
61
|
+
"CONTR. FECH.(2)": "OpenContractsEndSession", # At the end of the day
|
|
62
|
+
"NÚM. NEGOC.": "NumOfTrades",
|
|
63
|
+
"CONTR. NEGOC.": "TradedQuantity",
|
|
64
|
+
"VOL.": "FinancialVolume",
|
|
65
|
+
"AJUSTE": "SettlementPrice",
|
|
66
|
+
"AJUSTE ANTER. (3)": "PrevSettlementRate",
|
|
67
|
+
"AJUSTE CORRIG. (4)": "AdjSettlementRate",
|
|
68
|
+
"PREÇO MÍN.": "MinRate",
|
|
69
|
+
"PREÇO MÉD.": "AvgRate",
|
|
70
|
+
"PREÇO MÁX.": "MaxRate",
|
|
71
|
+
"PREÇO ABERTU.": "FirstRate",
|
|
72
|
+
"ÚLT. PREÇO": "LastRate",
|
|
73
|
+
"VAR. PTOS.": "PointsVariation",
|
|
74
|
+
# Attention: bid/ask rates are inverted
|
|
75
|
+
"ÚLT.OF. COMPRA": "LastAskRate",
|
|
76
|
+
"ÚLT.OF. VENDA": "LastBidRate",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
df = df.rename(columns=rename_dict)
|
|
80
|
+
|
|
81
|
+
df["TradeDate"] = trade_date
|
|
82
|
+
# Convert to datetime64[ns] since it is pandas default type for timestamps
|
|
83
|
+
df["TradeDate"] = df["TradeDate"].astype("datetime64[ns]")
|
|
84
|
+
|
|
85
|
+
# Contract code format was changed in 22/05/2006
|
|
86
|
+
if trade_date < pd.Timestamp("2006-05-22"):
|
|
87
|
+
df["ExpirationDate"] = df["ExpirationCode"].apply(
|
|
88
|
+
common.get_old_expiration_date, args=(trade_date,)
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
df["ExpirationDate"] = df["ExpirationCode"].apply(common.get_expiration_date)
|
|
92
|
+
|
|
93
|
+
df["DaysToExpiration"] = (df["ExpirationDate"] - trade_date).dt.days
|
|
94
|
+
# Convert to nullable integer, since other columns use this data type
|
|
95
|
+
df["DaysToExpiration"] = df["DaysToExpiration"].astype(pd.Int64Dtype())
|
|
96
|
+
# Remove expired contracts
|
|
97
|
+
df.query("DaysToExpiration > 0", inplace=True)
|
|
98
|
+
|
|
99
|
+
# Columns where 0 means NaN
|
|
100
|
+
cols_with_nan = [
|
|
101
|
+
"SettlementPrice",
|
|
102
|
+
"FirstRate",
|
|
103
|
+
"MinRate",
|
|
104
|
+
"MaxRate",
|
|
105
|
+
"AvgRate",
|
|
106
|
+
"LastRate",
|
|
107
|
+
"LastBidRate",
|
|
108
|
+
"LastAskRate",
|
|
109
|
+
]
|
|
110
|
+
for col in cols_with_nan:
|
|
111
|
+
df[col] = df[col].replace(0, pd.NA)
|
|
112
|
+
|
|
113
|
+
# Prior to 17/01/2002 (incluive), prices were not converted to rates
|
|
114
|
+
if trade_date <= pd.Timestamp("2002-01-17"):
|
|
115
|
+
df = _convert_prices_in_older_contracts(df)
|
|
116
|
+
|
|
117
|
+
df["SettlementRate"] = _convert_prices_to_rates(
|
|
118
|
+
df["SettlementPrice"], df["DaysToExpiration"]
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Remove percentage in all rate columns and round to 5 decimal places since it's the
|
|
122
|
+
# precision used by B3. Obs: 5 decimal places = 3 decimal places in percentage
|
|
123
|
+
rate_cols = [col for col in df.columns if "Rate" in col]
|
|
124
|
+
for col in rate_cols:
|
|
125
|
+
df[col] = (df[col] / 100).round(5)
|
|
126
|
+
|
|
127
|
+
# Filter and order columns
|
|
128
|
+
ordered_cols = [
|
|
129
|
+
"TradeDate",
|
|
130
|
+
"ExpirationCode",
|
|
131
|
+
"ExpirationDate",
|
|
132
|
+
"DaysToExpiration",
|
|
133
|
+
"OpenContracts",
|
|
134
|
+
# "OpenContractsEndSession" since there is no OpenContracts at the end of the
|
|
135
|
+
# day in XML data, it will be removed to avoid confusion with XML data
|
|
136
|
+
"NumOfTrades",
|
|
137
|
+
"TradedQuantity",
|
|
138
|
+
"FinancialVolume",
|
|
139
|
+
"SettlementPrice",
|
|
140
|
+
"MinRate",
|
|
141
|
+
"AvgRate",
|
|
142
|
+
"MaxRate",
|
|
143
|
+
"FirstRate",
|
|
144
|
+
"LastRate",
|
|
145
|
+
"LastAskRate",
|
|
146
|
+
"LastBidRate",
|
|
147
|
+
"SettlementRate",
|
|
148
|
+
]
|
|
149
|
+
return df[ordered_cols]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def fetch_ddi(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
|
|
153
|
+
"""
|
|
154
|
+
Fetchs the DDI futures data for a given date from B3.
|
|
155
|
+
|
|
156
|
+
This function fetches and processes the DI futures data from B3 for a specific
|
|
157
|
+
trade date. It's the primary external interface for accessing DI data.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
trade_date (pd.Timestamp): The trade date to fetch the DI futures data.
|
|
161
|
+
raw (bool): If True, returns the raw data as a Pandas pd.DataFrame.
|
|
162
|
+
Defaults to False.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
pd.DataFrame: A Pandas pd.DataFrame containing processed DI futures data.
|
|
166
|
+
|
|
167
|
+
Examples:
|
|
168
|
+
>>> from pyield.futures import di
|
|
169
|
+
>>> di.fetch_ddi(pd.Timestamp("2021-01-04"))
|
|
170
|
+
|
|
171
|
+
Notes:
|
|
172
|
+
- DaysToExpiration: number of business days to ExpirationDate.
|
|
173
|
+
- OpenContracts: number of open contracts at the start of the trading day.
|
|
174
|
+
"""
|
|
175
|
+
df_raw = common._fetch_raw_df(asset_code="DDI", trade_date=trade_date)
|
|
176
|
+
if return_raw:
|
|
177
|
+
return df_raw
|
|
178
|
+
return _process_raw_df(df_raw, trade_date)
|
|
@@ -1,121 +1,7 @@
|
|
|
1
|
-
import io
|
|
2
|
-
import warnings
|
|
3
|
-
|
|
4
1
|
import pandas as pd
|
|
5
|
-
import requests
|
|
6
2
|
|
|
7
3
|
from .. import bday
|
|
8
|
-
from . import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def get_old_expiration_date(
|
|
12
|
-
expiration_code: str, trade_date: pd.Timestamp
|
|
13
|
-
) -> pd.Timestamp:
|
|
14
|
-
"""
|
|
15
|
-
Internal function to convert an old DI contract code into its ExpirationDate date.
|
|
16
|
-
Valid for contract codes up to 21-05-2006.
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
expiration_code (str): An old DI Expiration Code from B3, where the first three
|
|
20
|
-
letters represent the month and the last digit represents the year.
|
|
21
|
-
Example: "JAN3".
|
|
22
|
-
trade_date (pd.Timestamp): The trade date for which the contract code is valid.
|
|
23
|
-
|
|
24
|
-
Returns:
|
|
25
|
-
pd.Timestamp
|
|
26
|
-
The contract's ExpirationDate date. Returns pd.NaT if the input is invalid.
|
|
27
|
-
|
|
28
|
-
Examples:
|
|
29
|
-
>>> get_old_expiration_date("JAN3", pd.Timestamp("2001-05-21"))
|
|
30
|
-
pd.Timestamp('2003-01-01')
|
|
31
|
-
|
|
32
|
-
Notes:
|
|
33
|
-
- In 22-05-2006, B3 changed the format of the DI contract codes. Before that
|
|
34
|
-
date, the first three letters represented the month and the last digit
|
|
35
|
-
represented the year.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
month_codes = {
|
|
39
|
-
"JAN": 1,
|
|
40
|
-
"FEV": 2,
|
|
41
|
-
"MAR": 3,
|
|
42
|
-
"ABR": 4,
|
|
43
|
-
"MAI": 5,
|
|
44
|
-
"JUN": 6,
|
|
45
|
-
"JUL": 7,
|
|
46
|
-
"AGO": 8,
|
|
47
|
-
"SET": 9,
|
|
48
|
-
"OUT": 10,
|
|
49
|
-
"NOV": 11,
|
|
50
|
-
"DEZ": 12,
|
|
51
|
-
}
|
|
52
|
-
try:
|
|
53
|
-
month_code = expiration_code[:3]
|
|
54
|
-
month = month_codes[month_code]
|
|
55
|
-
|
|
56
|
-
# Year codes must generated dynamically, since it depends on the trade date.
|
|
57
|
-
reference_year = trade_date.year
|
|
58
|
-
year_codes = {}
|
|
59
|
-
for year in range(reference_year, reference_year + 10):
|
|
60
|
-
year_codes[str(year)[-1:]] = year
|
|
61
|
-
year = year_codes[expiration_code[-1:]]
|
|
62
|
-
|
|
63
|
-
expiration_date = pd.Timestamp(year, month, 1)
|
|
64
|
-
# Adjust to the next business day when the date is a weekend or a holiday.
|
|
65
|
-
# Must use old holiday list, since this contract code was used until 2006.
|
|
66
|
-
return bday.offset_bdays(expiration_date, offset=0, holiday_list="old")
|
|
67
|
-
|
|
68
|
-
except (KeyError, ValueError):
|
|
69
|
-
return pd.NaT # type: ignore
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _get_raw_df(trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
73
|
-
"""
|
|
74
|
-
Internal function to fetch raw DI futures data from B3 for a specific trade date.
|
|
75
|
-
|
|
76
|
-
Args:
|
|
77
|
-
trade_date: a datetime-like object representing the trade date.
|
|
78
|
-
|
|
79
|
-
Returns:
|
|
80
|
-
pd.DataFrame: Raw DI data as a Pandas pd.DataFrame.
|
|
81
|
-
"""
|
|
82
|
-
# url example: https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data=05/10/2023&Mercadoria=DI1
|
|
83
|
-
url = f"https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data={trade_date.strftime('%d/%m/%Y')}&Mercadoria=DI1&XLS=false"
|
|
84
|
-
r = requests.get(url)
|
|
85
|
-
f = io.StringIO(r.text)
|
|
86
|
-
|
|
87
|
-
try:
|
|
88
|
-
# Attempt to get the first table with the header "AJUSTE"
|
|
89
|
-
df = pd.read_html(
|
|
90
|
-
f,
|
|
91
|
-
match="AJUSTE",
|
|
92
|
-
header=1,
|
|
93
|
-
thousands=".",
|
|
94
|
-
decimal=",",
|
|
95
|
-
na_values=["-"],
|
|
96
|
-
dtype_backend="numpy_nullable",
|
|
97
|
-
)[0]
|
|
98
|
-
|
|
99
|
-
# Remove rows with all NaN values
|
|
100
|
-
df = df.dropna(how="all")
|
|
101
|
-
|
|
102
|
-
# Remove columns with all NaN values
|
|
103
|
-
df = df.dropna(axis=1, how="all")
|
|
104
|
-
|
|
105
|
-
# Force "VAR. PTOS." to be string, since it can also be read as float
|
|
106
|
-
df["VAR. PTOS."] = df["VAR. PTOS."].astype(pd.StringDtype())
|
|
107
|
-
|
|
108
|
-
# Force "AJUSTE CORRIG. (4)" to be float, since it can be also read as int
|
|
109
|
-
df["AJUSTE CORRIG. (4)"] = df["AJUSTE CORRIG. (4)"].astype(pd.Float64Dtype())
|
|
110
|
-
|
|
111
|
-
return df
|
|
112
|
-
|
|
113
|
-
except Exception as e:
|
|
114
|
-
trade_date_str = trade_date.strftime("%d/%m/%Y")
|
|
115
|
-
message = f"""A {type(e).__name__} occurred while reading the DI futures data
|
|
116
|
-
for {trade_date_str}. Returning an empty pd.DataFrame."""
|
|
117
|
-
warnings.warn(message, stacklevel=2)
|
|
118
|
-
return pd.DataFrame()
|
|
4
|
+
from . import common
|
|
119
5
|
|
|
120
6
|
|
|
121
7
|
def _convert_prices_to_rates(prices: pd.Series, bd: pd.Series) -> pd.Series:
|
|
@@ -147,7 +33,7 @@ def _convert_prices_in_older_contracts(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
147
33
|
"LastAskRate",
|
|
148
34
|
]
|
|
149
35
|
for col in convert_cols:
|
|
150
|
-
df[col] = _convert_prices_to_rates(df[col], df["
|
|
36
|
+
df[col] = _convert_prices_to_rates(df[col], df["BDaysToExpiration"])
|
|
151
37
|
|
|
152
38
|
# Invert low and high prices
|
|
153
39
|
df["MinRate"], df["MaxRate"] = df["MaxRate"], df["MinRate"]
|
|
@@ -200,16 +86,16 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
200
86
|
# Contract code format was changed in 22/05/2006
|
|
201
87
|
if trade_date < pd.Timestamp("2006-05-22"):
|
|
202
88
|
df["ExpirationDate"] = df["ExpirationCode"].apply(
|
|
203
|
-
get_old_expiration_date, args=(trade_date,)
|
|
89
|
+
common.get_old_expiration_date, args=(trade_date,)
|
|
204
90
|
)
|
|
205
91
|
else:
|
|
206
|
-
df["ExpirationDate"] = df["ExpirationCode"].apply(
|
|
92
|
+
df["ExpirationDate"] = df["ExpirationCode"].apply(common.get_expiration_date)
|
|
207
93
|
|
|
208
|
-
df["
|
|
94
|
+
df["BDaysToExpiration"] = bday.count_bdays(trade_date, df["ExpirationDate"])
|
|
209
95
|
# Convert to nullable integer, since other columns use this data type
|
|
210
|
-
df["
|
|
96
|
+
df["BDaysToExpiration"] = df["BDaysToExpiration"].astype(pd.Int64Dtype())
|
|
211
97
|
# Remove expired contracts
|
|
212
|
-
df.query("
|
|
98
|
+
df.query("BDaysToExpiration > 0", inplace=True)
|
|
213
99
|
|
|
214
100
|
# Columns where 0 means NaN
|
|
215
101
|
cols_with_nan = [
|
|
@@ -230,7 +116,7 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
230
116
|
df = _convert_prices_in_older_contracts(df)
|
|
231
117
|
|
|
232
118
|
df["SettlementRate"] = _convert_prices_to_rates(
|
|
233
|
-
df["SettlementPrice"], df["
|
|
119
|
+
df["SettlementPrice"], df["BDaysToExpiration"]
|
|
234
120
|
)
|
|
235
121
|
|
|
236
122
|
# Remove percentage in all rate columns and round to 5 decimal places since it's the
|
|
@@ -244,7 +130,7 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
244
130
|
"TradeDate",
|
|
245
131
|
"ExpirationCode",
|
|
246
132
|
"ExpirationDate",
|
|
247
|
-
"
|
|
133
|
+
"BDaysToExpiration",
|
|
248
134
|
"OpenContracts",
|
|
249
135
|
# "OpenContractsEndSession" since there is no OpenContracts at the end of the
|
|
250
136
|
# day in XML data, it will be removed to avoid confusion with XML data
|
|
@@ -264,15 +150,15 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
264
150
|
return df[ordered_cols]
|
|
265
151
|
|
|
266
152
|
|
|
267
|
-
def
|
|
153
|
+
def fetch_di(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
|
|
268
154
|
"""
|
|
269
|
-
|
|
155
|
+
Fetchs the DI futures data for a given date from B3.
|
|
270
156
|
|
|
271
157
|
This function fetches and processes the DI futures data from B3 for a specific
|
|
272
158
|
trade date. It's the primary external interface for accessing DI data.
|
|
273
159
|
|
|
274
160
|
Args:
|
|
275
|
-
trade_date:
|
|
161
|
+
trade_date (pd.Timestamp): The trade date to fetch the DI futures data.
|
|
276
162
|
raw (bool): If True, returns the raw data as a Pandas pd.DataFrame.
|
|
277
163
|
Defaults to False.
|
|
278
164
|
|
|
@@ -280,14 +166,14 @@ def get_di(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
|
|
|
280
166
|
pd.DataFrame: A Pandas pd.DataFrame containing processed DI futures data.
|
|
281
167
|
|
|
282
168
|
Examples:
|
|
283
|
-
>>>
|
|
169
|
+
>>> from pyield.futures import di
|
|
170
|
+
>>> di.fetch_di(pd.Timestamp("2021-01-04"))
|
|
284
171
|
|
|
285
172
|
Notes:
|
|
286
|
-
-
|
|
173
|
+
- BDaysToExpiration: number of business days to ExpirationDate.
|
|
287
174
|
- OpenContracts: number of open contracts at the start of the trading day.
|
|
288
|
-
- closed_contracts: number of closed contracts at the end of the trading day.
|
|
289
175
|
"""
|
|
290
|
-
df_raw =
|
|
176
|
+
df_raw = common._fetch_raw_df(asset_code="DI1", trade_date=trade_date)
|
|
291
177
|
if return_raw:
|
|
292
178
|
return df_raw
|
|
293
179
|
return _process_raw_df(df_raw, trade_date)
|
|
@@ -8,7 +8,7 @@ from lxml import etree
|
|
|
8
8
|
from pandas import DataFrame, Timestamp
|
|
9
9
|
|
|
10
10
|
from .. import bday
|
|
11
|
-
from . import
|
|
11
|
+
from . import common as cm
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def _get_file_from_url(trade_date: Timestamp, source_type: str) -> io.BytesIO:
|
|
@@ -97,12 +97,12 @@ def _extract_di_data_from_xml(xml_file: io.BytesIO) -> list[dict]:
|
|
|
97
97
|
# Extrair a data de negociação
|
|
98
98
|
if price_report is None:
|
|
99
99
|
continue
|
|
100
|
-
trade_date = price_report.find(".//ns:
|
|
100
|
+
trade_date = price_report.find(".//ns:TradeDate/ns:Dt", namespaces)
|
|
101
101
|
|
|
102
102
|
# Preparar o dicionário de dados do ticker com a data de negociação
|
|
103
103
|
if trade_date is None:
|
|
104
104
|
continue
|
|
105
|
-
ticker_data = {"
|
|
105
|
+
ticker_data = {"TradeDate": trade_date.text, "TckrSymb": tckr_symb.text}
|
|
106
106
|
|
|
107
107
|
# Acessar o elemento FinInstrmAttrbts que contém o TckrSymb
|
|
108
108
|
fin_instrm_attrbts = price_report.find(".//ns:FinInstrmAttrbts", namespaces)
|
|
@@ -229,17 +229,17 @@ def _process_di_df(df_raw: DataFrame) -> DataFrame:
|
|
|
229
229
|
# Convert to datetime64[ns] since it is pandas default type for timestamps
|
|
230
230
|
df["TradDt"] = df["TradDt"].astype("datetime64[ns]")
|
|
231
231
|
|
|
232
|
-
expiration = df["TckrSymb"].str[3:].apply(
|
|
232
|
+
expiration = df["TckrSymb"].str[3:].apply(cm.get_expiration_date)
|
|
233
233
|
df.insert(2, "ExpirationDate", expiration)
|
|
234
234
|
|
|
235
235
|
business_days = bday.count_bdays(df["TradDt"], df["ExpirationDate"])
|
|
236
|
-
df.insert(3, "
|
|
236
|
+
df.insert(3, "BDaysToExpiration", business_days)
|
|
237
237
|
|
|
238
238
|
# Convert to nullable integer, since other columns use this data type
|
|
239
|
-
df["
|
|
239
|
+
df["BDaysToExpiration"] = df["BDaysToExpiration"].astype(pd.Int64Dtype())
|
|
240
240
|
|
|
241
241
|
# Remove expired contracts
|
|
242
|
-
df.query("
|
|
242
|
+
df.query("BDaysToExpiration > 0", inplace=True)
|
|
243
243
|
|
|
244
244
|
return df.sort_values(by=["ExpirationDate"], ignore_index=True)
|
|
245
245
|
|
|
@@ -307,3 +307,43 @@ def read_di(file_path: Path, return_raw: bool = False) -> DataFrame:
|
|
|
307
307
|
|
|
308
308
|
else:
|
|
309
309
|
raise ValueError("A file path must be provided.")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def read_file(file_path: Path, return_raw: bool = False) -> pd.DataFrame:
|
|
313
|
+
"""
|
|
314
|
+
Reads DI futures data from a file and returns it as a pandas DataFrame.
|
|
315
|
+
|
|
316
|
+
This function opens and reads a DI futures data file, returning the contents as a
|
|
317
|
+
pandas DataFrame. It supports reading from both XML files provided by B3, wich
|
|
318
|
+
are the simplified and complete Price Reports.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
file_path (Path): The file path to the DI data file. This should be a valid
|
|
322
|
+
Path object pointing to the location of the file.
|
|
323
|
+
return_raw (bool, optional): If set to True, the function returns the raw data
|
|
324
|
+
without applying any transformation or processing. Useful for cases where
|
|
325
|
+
raw data inspection or custom processing is needed. Defaults to False.
|
|
326
|
+
source_type (Literal["bmf", "b3", "b3s"], optional): Indicates the source of
|
|
327
|
+
the data. Defaults to "bmf". Options include:
|
|
328
|
+
- "bmf": Fetches data from the old BM&FBOVESPA website. Fastest option.
|
|
329
|
+
- "b3": Fetches data from the complete Price Report (XML file) provided
|
|
330
|
+
by B3.
|
|
331
|
+
- "b3s": Fetches data from the simplified Price Report (XML file)
|
|
332
|
+
provided by B3. Faster than "b3" but less detailed.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
pd.DataFrame: A DataFrame containing the processed or raw DI futures data,
|
|
336
|
+
depending on the `return_raw` flag.
|
|
337
|
+
|
|
338
|
+
Examples:
|
|
339
|
+
>>> read_di(Path("path/to/di_data_file.xml"))
|
|
340
|
+
# returns a DataFrame with the DI futures data
|
|
341
|
+
|
|
342
|
+
>>> read_di(Path("path/to/di_data_file.xml"), return_raw=True)
|
|
343
|
+
# returns a DataFrame with the raw DI futures data, without processing
|
|
344
|
+
|
|
345
|
+
Note:
|
|
346
|
+
The ability to process and return raw data is primarily intended for advanced
|
|
347
|
+
users who require access to the data in its original form for custom analyses.
|
|
348
|
+
"""
|
|
349
|
+
return read_di(file_path, return_raw=return_raw)
|
|
@@ -3,7 +3,7 @@ import io
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import requests
|
|
5
5
|
|
|
6
|
-
from . import
|
|
6
|
+
from . import futures as ft
|
|
7
7
|
|
|
8
8
|
# URL Constants
|
|
9
9
|
ANBIMA_NON_MEMBER_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs/"
|
|
@@ -100,7 +100,7 @@ def _process_raw_df(df_raw: pd.DataFrame) -> pd.DataFrame:
|
|
|
100
100
|
return df.sort_values(["BondType", "MaturityDate"], ignore_index=True)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
def
|
|
103
|
+
def fetch_bonds(reference_date: pd.Timestamp, return_raw=False) -> pd.DataFrame:
|
|
104
104
|
"""
|
|
105
105
|
Fetches indicative treasury rates from ANBIMA for a specified reference date.
|
|
106
106
|
|
|
@@ -150,7 +150,7 @@ def calculate_di_spreads(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
150
150
|
bond type and maturity date.
|
|
151
151
|
"""
|
|
152
152
|
# Fetch DI rates and adjust the maturity date format for compatibility
|
|
153
|
-
df_di =
|
|
153
|
+
df_di = ft.fetch_di(reference_date)[["ExpirationDate", "SettlementRate"]]
|
|
154
154
|
|
|
155
155
|
# Renaming the columns to match the ANBIMA structure
|
|
156
156
|
df_di.rename(columns={"ExpirationDate": "MaturityDate"}, inplace=True)
|
|
@@ -159,7 +159,7 @@ def calculate_di_spreads(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
159
159
|
df_di["MaturityDate"] = df_di["MaturityDate"].dt.to_period("M").dt.to_timestamp()
|
|
160
160
|
|
|
161
161
|
# Fetch bond rates, filtering for LTN and NTN-F types
|
|
162
|
-
df_anbima =
|
|
162
|
+
df_anbima = fetch_bonds(reference_date, False)
|
|
163
163
|
df_anbima.query("BondType in ['LTN', 'NTN-F']", inplace=True)
|
|
164
164
|
|
|
165
165
|
# Merge bond and DI rates by maturity date to calculate spreads
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
import pytest
|
|
3
3
|
|
|
4
|
-
from pyield
|
|
5
|
-
from pyield.di import web as web
|
|
4
|
+
from pyield import futures as ft
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
def test_valid_old_contract_code1():
|
|
9
8
|
expiration_code = "JAN3" # Valid contract code
|
|
10
9
|
trade_date = pd.Timestamp("2001-05-21")
|
|
11
|
-
result =
|
|
10
|
+
result = ft.get_old_expiration_date(expiration_code, trade_date)
|
|
12
11
|
contract_expiration = pd.Timestamp("2003-01-02")
|
|
13
12
|
assert result == contract_expiration
|
|
14
13
|
|
|
@@ -16,7 +15,7 @@ def test_valid_old_contract_code1():
|
|
|
16
15
|
def test_valid_old_contract_code2():
|
|
17
16
|
expiration_code = "JAN3" # Valid contract code
|
|
18
17
|
trade_date = pd.Timestamp("1990-01-01")
|
|
19
|
-
result =
|
|
18
|
+
result = ft.get_old_expiration_date(expiration_code, trade_date)
|
|
20
19
|
contract_expiration = pd.Timestamp("1993-01-04")
|
|
21
20
|
assert result == contract_expiration
|
|
22
21
|
|
|
@@ -25,13 +24,13 @@ def test_invalid_old_contract_code():
|
|
|
25
24
|
expiration_code = "J3" # Invalid contract code
|
|
26
25
|
trade_date = pd.Timestamp("2001-01-02")
|
|
27
26
|
# Must return NaT
|
|
28
|
-
result =
|
|
27
|
+
result = ft.get_old_expiration_date(expiration_code, trade_date)
|
|
29
28
|
assert pd.isnull(result)
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
def test_new_contract_code():
|
|
33
32
|
expiration_code = "F23" # Valid contract code
|
|
34
|
-
result =
|
|
33
|
+
result = ft.get_expiration_date(expiration_code)
|
|
35
34
|
contract_expiration = pd.Timestamp("2023-01-02")
|
|
36
35
|
assert result == contract_expiration
|
|
37
36
|
|
|
@@ -43,7 +42,8 @@ def test_settlement_rate_with_old_holiday_list():
|
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
# 22-12-2023 is before the new holiday calendar
|
|
46
|
-
|
|
45
|
+
test_date = pd.Timestamp("2023-12-22")
|
|
46
|
+
df = ft.fetch_di(trade_date=test_date)
|
|
47
47
|
expiration_codes = list(settlement_rates.keys()) # noqa: F841
|
|
48
48
|
result = df.query("ExpirationCode in @expiration_codes")["SettlementRate"].to_list()
|
|
49
49
|
assert result == list(settlement_rates.values())
|
|
@@ -67,8 +67,8 @@ def test_settlement_rates_with_current_holiday_list():
|
|
|
67
67
|
"F31": 0.10240,
|
|
68
68
|
"F33": 0.10331,
|
|
69
69
|
}
|
|
70
|
-
|
|
71
|
-
df =
|
|
70
|
+
test_date = pd.Timestamp("2023-12-26")
|
|
71
|
+
df = ft.fetch_di(trade_date=test_date)
|
|
72
72
|
expiration_codes = list(settlement_rates.keys()) # noqa: F841
|
|
73
73
|
results = df.query("ExpirationCode in @expiration_codes")[
|
|
74
74
|
"SettlementRate"
|
|
@@ -77,6 +77,6 @@ def test_settlement_rates_with_current_holiday_list():
|
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
def test_non_business_day():
|
|
80
|
-
non_business_day = "2023-12-24"
|
|
80
|
+
non_business_day = pd.Timestamp("2023-12-24")
|
|
81
81
|
with pytest.raises(ValueError):
|
|
82
|
-
|
|
82
|
+
ft.fetch_di(trade_date=non_business_day)
|
pyield-0.7.0/pyield/__about__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.7.0"
|
pyield-0.7.0/pyield/di/core.py
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from typing import Literal
|
|
3
|
-
|
|
4
|
-
import pandas as pd
|
|
5
|
-
|
|
6
|
-
from .. import bday
|
|
7
|
-
from . import web, xml
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _normalize_date(trade_date: str | pd.Timestamp | None = None) -> pd.Timestamp:
|
|
11
|
-
if isinstance(trade_date, str):
|
|
12
|
-
normalized_date = pd.Timestamp(trade_date).normalize()
|
|
13
|
-
elif isinstance(trade_date, pd.Timestamp):
|
|
14
|
-
normalized_date = trade_date.normalize()
|
|
15
|
-
elif trade_date is None:
|
|
16
|
-
today = pd.Timestamp.today().normalize()
|
|
17
|
-
# Get last business day before today
|
|
18
|
-
normalized_date = bday.offset_bdays(today, -1)
|
|
19
|
-
else:
|
|
20
|
-
raise ValueError("Invalid date format.")
|
|
21
|
-
|
|
22
|
-
# Raise an error if the trade date is in the future
|
|
23
|
-
if normalized_date > pd.Timestamp.today().normalize():
|
|
24
|
-
raise ValueError("Trade date cannot be in the future.")
|
|
25
|
-
|
|
26
|
-
# Raise error if the reference date is not a business day
|
|
27
|
-
if not bday.is_bday(normalized_date):
|
|
28
|
-
raise ValueError("Trade date must be a business day.")
|
|
29
|
-
|
|
30
|
-
return normalized_date
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def get_expiration_date(expiration_code: str) -> pd.Timestamp:
|
|
34
|
-
"""
|
|
35
|
-
Converts an expiration code into its corresponding expiration date.
|
|
36
|
-
|
|
37
|
-
This function translates an expiration code into a specific expiration date based on
|
|
38
|
-
a given mapping. The expiration code consists of a letter representing the month and
|
|
39
|
-
two digits for the year. The function ensures the date returned is a valid business
|
|
40
|
-
day by adjusting weekends and holidays as necessary.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
expiration_code (str): The expiration code to be converted, where the first
|
|
44
|
-
letter represents the month and the last two digits represent the year
|
|
45
|
-
(e.g., "F23" for January 2023).
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
pd.Timestamp: The expiration date corresponding to the code, adjusted to a valid
|
|
49
|
-
business day. Returns pd.NaT if the code is invalid.
|
|
50
|
-
|
|
51
|
-
Examples:
|
|
52
|
-
>>> get_expiration_date("F23")
|
|
53
|
-
pd.Timestamp('2023-01-01')
|
|
54
|
-
|
|
55
|
-
>>> get_expiration_date("Z33")
|
|
56
|
-
pd.Timestamp('2033-12-01')
|
|
57
|
-
|
|
58
|
-
>>> get_expiration_date("A99")
|
|
59
|
-
pd.NaT
|
|
60
|
-
|
|
61
|
-
Notes:
|
|
62
|
-
The expiration date is calculated based on the format change introduced by B3 on
|
|
63
|
-
22-05-2006, where the first letter represents the month and the last two digits
|
|
64
|
-
represent the year.
|
|
65
|
-
"""
|
|
66
|
-
month_codes = {
|
|
67
|
-
"F": 1,
|
|
68
|
-
"G": 2,
|
|
69
|
-
"H": 3,
|
|
70
|
-
"J": 4,
|
|
71
|
-
"K": 5,
|
|
72
|
-
"M": 6,
|
|
73
|
-
"N": 7,
|
|
74
|
-
"Q": 8,
|
|
75
|
-
"U": 9,
|
|
76
|
-
"V": 10,
|
|
77
|
-
"X": 11,
|
|
78
|
-
"Z": 12,
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
try:
|
|
82
|
-
month_code = expiration_code[0]
|
|
83
|
-
month = month_codes[month_code]
|
|
84
|
-
year = int("20" + expiration_code[-2:])
|
|
85
|
-
# The expiration date is always the first business day of the month
|
|
86
|
-
expiration = pd.Timestamp(year, month, 1)
|
|
87
|
-
|
|
88
|
-
# Adjust to the next business day when expiration date is a weekend or a holiday
|
|
89
|
-
adj_expiration = bday.offset_bdays(expiration, offset=0)
|
|
90
|
-
|
|
91
|
-
return adj_expiration
|
|
92
|
-
|
|
93
|
-
except (KeyError, ValueError):
|
|
94
|
-
return pd.NaT # type: ignore
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def fetch_data(
|
|
98
|
-
trade_date: str | pd.Timestamp | None = None,
|
|
99
|
-
source_type: Literal["bmf", "b3", "b3s"] = "bmf",
|
|
100
|
-
return_raw: bool = False,
|
|
101
|
-
) -> pd.DataFrame:
|
|
102
|
-
"""
|
|
103
|
-
Fetches DI futures data for a specified trade date from B3.
|
|
104
|
-
|
|
105
|
-
Retrieves and processes DI futures data from B3 for a given trade date. This
|
|
106
|
-
function serves as the primary method for accessing DI data, with options to
|
|
107
|
-
specify the source of the data and whether to return raw data.
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
trade_date (str | pd.Timestamp | None, optional): The trade date for which to
|
|
111
|
-
fetch DI data. If None or not provided, uses the previous business day.
|
|
112
|
-
source_type (Literal["bmf", "b3", "b3s"], optional): Indicates the source of
|
|
113
|
-
the data. Defaults to "bmf". Options include:
|
|
114
|
-
- "bmf": Fetches data from the old BM&FBOVESPA website. Fastest option.
|
|
115
|
-
- "b3": Fetches data from the complete Price Report (XML file) provided
|
|
116
|
-
by B3.
|
|
117
|
-
- "b3s": Fetches data from the simplified Price Report (XML file)
|
|
118
|
-
provided by B3. Faster than "b3" but less detailed.
|
|
119
|
-
return_raw (bool, optional): If True, returns the raw DI data without
|
|
120
|
-
processing.
|
|
121
|
-
|
|
122
|
-
Returns:
|
|
123
|
-
pd.DataFrame: A DataFrame containing the DI futures data for the specified
|
|
124
|
-
trade
|
|
125
|
-
date. Format and content depend on the source_type and return_raw flag.
|
|
126
|
-
|
|
127
|
-
Examples:
|
|
128
|
-
# Fetch DI data for the previous business day using default settings
|
|
129
|
-
>>> get_di()
|
|
130
|
-
|
|
131
|
-
# Fetch DI data for a specific trade date from the simplified B3 Price Report
|
|
132
|
-
>>> get_di("2023-12-28", source_type="b3s")
|
|
133
|
-
|
|
134
|
-
Notes:
|
|
135
|
-
- Complete Price Report XML files are about 5 MB in size.
|
|
136
|
-
- Simplified Price Report XML files are significantly smaller, around 50 kB.
|
|
137
|
-
- For file specifications, refer to the B3 documentation: [B3 File Specs](https://www.b3.com.br/data/files/16/70/29/9C/6219D710C8F297D7AC094EA8/Catalogo_precos_v1.3.pdf)
|
|
138
|
-
"""
|
|
139
|
-
# Force trade_date to be a pandas pd.Timestamp
|
|
140
|
-
normalized_trade_date = _normalize_date(trade_date)
|
|
141
|
-
|
|
142
|
-
if source_type == "bmf":
|
|
143
|
-
return web.get_di(normalized_trade_date, return_raw)
|
|
144
|
-
elif source_type in ["b3", "b3s"]:
|
|
145
|
-
return xml.read_xml(normalized_trade_date, source_type, return_raw)
|
|
146
|
-
else:
|
|
147
|
-
raise ValueError("source_type must be either 'bmf', 'b3' or 'b3s'.")
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def read_data(file_path: Path, return_raw: bool = False) -> pd.DataFrame:
|
|
151
|
-
"""
|
|
152
|
-
Reads DI futures data from a file and returns it as a pandas DataFrame.
|
|
153
|
-
|
|
154
|
-
This function opens and reads a DI futures data file, returning the contents as a
|
|
155
|
-
pandas DataFrame. It supports reading from both XML files provided by B3, wich
|
|
156
|
-
are the simplified and complete Price Reports.
|
|
157
|
-
|
|
158
|
-
Args:
|
|
159
|
-
file_path (Path): The file path to the DI data file. This should be a valid
|
|
160
|
-
Path object pointing to the location of the file.
|
|
161
|
-
return_raw (bool, optional): If set to True, the function returns the raw data
|
|
162
|
-
without applying any transformation or processing. Useful for cases where
|
|
163
|
-
raw data inspection or custom processing is needed. Defaults to False.
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
pd.DataFrame: A DataFrame containing the processed or raw DI futures data,
|
|
167
|
-
depending on the `return_raw` flag.
|
|
168
|
-
|
|
169
|
-
Examples:
|
|
170
|
-
>>> read_di(Path("path/to/di_data_file.xml"))
|
|
171
|
-
# returns a DataFrame with the DI futures data
|
|
172
|
-
|
|
173
|
-
>>> read_di(Path("path/to/di_data_file.xml"), return_raw=True)
|
|
174
|
-
# returns a DataFrame with the raw DI futures data, without processing
|
|
175
|
-
|
|
176
|
-
Note:
|
|
177
|
-
The ability to process and return raw data is primarily intended for advanced
|
|
178
|
-
users who require access to the data in its original form for custom analyses.
|
|
179
|
-
"""
|
|
180
|
-
return xml.read_di(file_path, return_raw=return_raw)
|
|
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
|