PYield 0.6.0__tar.gz → 0.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. {pyield-0.6.0 → pyield-0.7.1}/PKG-INFO +54 -39
  2. {pyield-0.6.0 → pyield-0.7.1}/README.md +53 -38
  3. pyield-0.7.1/pyield/__about__.py +1 -0
  4. {pyield-0.6.0 → pyield-0.7.1}/pyield/__init__.py +3 -2
  5. pyield-0.7.1/pyield/data_access.py +89 -0
  6. pyield-0.7.1/pyield/futures/__init__.py +10 -0
  7. pyield-0.7.1/pyield/futures/common.py +175 -0
  8. pyield-0.7.1/pyield/futures/ddi.py +178 -0
  9. pyield-0.6.0/pyield/di/web.py → pyield-0.7.1/pyield/futures/di.py +16 -130
  10. pyield-0.6.0/pyield/di/xml.py → pyield-0.7.1/pyield/futures/di_xml.py +47 -7
  11. pyield-0.7.1/pyield/indicators.py +59 -0
  12. {pyield-0.6.0 → pyield-0.7.1}/pyield/treasuries.py +7 -23
  13. pyield-0.7.1/pyield/utils.py +55 -0
  14. {pyield-0.6.0 → pyield-0.7.1}/pyproject.toml +1 -1
  15. pyield-0.6.0/tests/test_di.py → pyield-0.7.1/tests/test_futures.py +11 -11
  16. pyield-0.6.0/pyield/__about__.py +0 -1
  17. pyield-0.6.0/pyield/data_access.py +0 -51
  18. pyield-0.6.0/pyield/di/__init__.py +0 -6
  19. pyield-0.6.0/pyield/di/core.py +0 -180
  20. pyield-0.6.0/pyield/utils.py +0 -26
  21. {pyield-0.6.0 → pyield-0.7.1}/LICENSE +0 -0
  22. {pyield-0.6.0 → pyield-0.7.1}/pyield/bday.py +0 -0
  23. {pyield-0.6.0 → pyield-0.7.1}/pyield/data_analysis.py +0 -0
  24. {pyield-0.6.0 → pyield-0.7.1}/pyield/holidays/__init__.py +0 -0
  25. {pyield-0.6.0 → pyield-0.7.1}/pyield/holidays/br_holidays_new.txt +0 -0
  26. {pyield-0.6.0 → pyield-0.7.1}/pyield/holidays/br_holidays_old.txt +0 -0
  27. {pyield-0.6.0 → pyield-0.7.1}/pyield/holidays/core.py +0 -0
  28. {pyield-0.6.0 → pyield-0.7.1}/pyield/py.typed +0 -0
  29. {pyield-0.6.0 → pyield-0.7.1}/tests/__init__.py +0 -0
  30. pyield-0.6.0/tests/test_br_calendar.py → pyield-0.7.1/tests/test_bday.py +1 -1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PYield
3
- Version: 0.6.0
3
+ Version: 0.7.1
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
 
@@ -67,29 +67,56 @@ pip install pyield
67
67
 
68
68
  ## How to use PYield
69
69
 
70
+ ### Business Days Tools (Brazilian holidays are automatically considered)
71
+ ```python
72
+ >>> import pyield as yd
73
+
74
+ # Count the number of business days between two dates
75
+ # Start date is included, end date is excluded
76
+ >>> yd.count_bdays(start='2023-12-29', end='2024-01-02')
77
+ 1
78
+
79
+ # Get the next business day after a given date (offset=1)
80
+ >>> yd.offset_bdays(dates="2023-12-29", offset=1)
81
+ Timestamp('2024-01-02 00:00:00')
82
+
83
+ # Get the next business day if it is not a business day (offset=0)
84
+ >>> yd.offset_bdays(dates="2023-12-30", offset=0)
85
+ Timestamp('2024-01-02 00:00:00')
86
+
87
+ # Since 2023-12-29 is a business day, it returns the same date (offset=0)
88
+ >>> yd.offset_bdays(dates="2023-12-29", offset=0)
89
+ Timestamp('2023-12-29 00:00:00')
90
+
91
+ # Generate a pandas series with the business days between two dates
92
+ >>> yd.generate_bdays(start='2023-12-29', end='2024-01-03')
93
+ 0 2023-12-29
94
+ 1 2024-01-02
95
+ 2 2024-01-03
96
+ dtype: datetime64[ns]
97
+ ```
98
+
70
99
  ### Futures Data
71
100
  ```python
72
- import pyield as yd
73
-
74
- # Get a DataFrame with the DI Futures data from B3
75
- >>> yd.get_data(asset_code="DI1", reference_date='2024-03-08')
76
-
77
- TradeDate ExpirationCode ExpirationDate BDToExpiration ... LastRate LastAskRate LastBidRate SettlementRate
78
- 2024-03-08 J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
79
- 2024-03-08 K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
80
- 2024-03-08 M24 2024-06-03 58 ... 10.604 10.602 10.604 10.608
81
- ... ... ... ... ... ... ... ... ...
82
- 2024-03-08 F37 2037-01-02 3213 ... <NA> <NA> <NA> 10.859
83
- 2024-03-08 F38 2038-01-04 3462 ... <NA> <NA> <NA> 10.859
84
- 2024-03-08 F39 2039-01-03 3713 ... <NA> <NA> <NA> 10.85
101
+ # Fetch a DataFrame with the DI Futures data from B3
102
+ >>> yd.fetch_asset(asset_code="DI1", reference_date='2024-03-08')
103
+
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
85
112
  ```
86
113
 
87
114
  ### Treasury Bonds Data
88
115
  ```python
89
- # Get a DataFrame with the NTN-B data from ANBIMA
116
+ # Fetch a DataFrame with the NTN-B data from ANBIMA
90
117
  # Anbima data is available for the last 5 working days
91
118
  # Obs: Anbima members have access to the full history
92
- >>> yd.get_data(asset_code="NTN-B", reference_date='2024-04-12')
119
+ >>> yd.fetch_asset(asset_code="NTN-B", reference_date='2024-04-12')
93
120
 
94
121
  BondType ReferenceDate MaturityDate BidRate AskRate IndicativeRate Price
95
122
  NTN-B 2024-04-12 2024-08-15 0.07540 0.07504 0.07523 4,271.43565
@@ -116,29 +143,17 @@ NTN-F 2024-04-11 2033-01-01 -5.69000
116
143
  NTN-F 2024-04-11 2035-01-01 -1.27000
117
144
  ```
118
145
 
119
- ### Business Days Tools (Brazilian holidays are automatically considered)
146
+ ### Indicators Data
120
147
  ```python
121
- # Count the number of business days between two dates
122
- # Start date is included, end date is excluded
123
- >>> yd.count_bdays(start='2023-12-29', end='2024-01-02')
124
- 1
148
+ # Fetch the SELIC target rate from the Central Bank of Brazil
149
+ >>> yd.fetch_indicator(indicator_code="SELIC", reference_date='2024-04-12')
150
+ 10.75
125
151
 
126
- # Get the next business day after a given date (offset=1)
127
- >>> yd.offset_bdays(dates="2023-12-29", offset=1)
128
- Timestamp('2024-01-02 00:00:00')
152
+ # Fetch the IPCA monthly inflation rate from IBGE
153
+ >>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-03-18')
154
+ 0.16
129
155
 
130
- # Get the next business day if it is not a business day (offset=0)
131
- >>> yd.offset_bdays(dates="2023-12-30", offset=0)
132
- Timestamp('2024-01-02 00:00:00')
133
-
134
- # Since 2023-12-29 is a business day, it returns the same date (offset=0)
135
- >>> yd.offset_bdays(dates="2023-12-29", offset=0)
136
- Timestamp('2023-12-29 00:00:00')
137
-
138
- # Generate a pandas series with the business days between two dates
139
- >>> yd.generate_bdays(start='2023-12-29', end='2024-01-03')
140
- 0 2023-12-29
141
- 1 2024-01-02
142
- 2 2024-01-03
143
- dtype: datetime64[ns]
156
+ # If no data is yet available for the indicator, the function returns None
157
+ >>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-04-10')
158
+ None
144
159
  ```
@@ -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
 
@@ -25,29 +25,56 @@ pip install pyield
25
25
 
26
26
  ## How to use PYield
27
27
 
28
+ ### Business Days Tools (Brazilian holidays are automatically considered)
29
+ ```python
30
+ >>> import pyield as yd
31
+
32
+ # Count the number of business days between two dates
33
+ # Start date is included, end date is excluded
34
+ >>> yd.count_bdays(start='2023-12-29', end='2024-01-02')
35
+ 1
36
+
37
+ # Get the next business day after a given date (offset=1)
38
+ >>> yd.offset_bdays(dates="2023-12-29", offset=1)
39
+ Timestamp('2024-01-02 00:00:00')
40
+
41
+ # Get the next business day if it is not a business day (offset=0)
42
+ >>> yd.offset_bdays(dates="2023-12-30", offset=0)
43
+ Timestamp('2024-01-02 00:00:00')
44
+
45
+ # Since 2023-12-29 is a business day, it returns the same date (offset=0)
46
+ >>> yd.offset_bdays(dates="2023-12-29", offset=0)
47
+ Timestamp('2023-12-29 00:00:00')
48
+
49
+ # Generate a pandas series with the business days between two dates
50
+ >>> yd.generate_bdays(start='2023-12-29', end='2024-01-03')
51
+ 0 2023-12-29
52
+ 1 2024-01-02
53
+ 2 2024-01-03
54
+ dtype: datetime64[ns]
55
+ ```
56
+
28
57
  ### Futures Data
29
58
  ```python
30
- import pyield as yd
31
-
32
- # Get a DataFrame with the DI Futures data from B3
33
- >>> yd.get_data(asset_code="DI1", reference_date='2024-03-08')
34
-
35
- TradeDate ExpirationCode ExpirationDate BDToExpiration ... LastRate LastAskRate LastBidRate SettlementRate
36
- 2024-03-08 J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
37
- 2024-03-08 K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
38
- 2024-03-08 M24 2024-06-03 58 ... 10.604 10.602 10.604 10.608
39
- ... ... ... ... ... ... ... ... ...
40
- 2024-03-08 F37 2037-01-02 3213 ... <NA> <NA> <NA> 10.859
41
- 2024-03-08 F38 2038-01-04 3462 ... <NA> <NA> <NA> 10.859
42
- 2024-03-08 F39 2039-01-03 3713 ... <NA> <NA> <NA> 10.85
59
+ # Fetch a DataFrame with the DI Futures data from B3
60
+ >>> yd.fetch_asset(asset_code="DI1", reference_date='2024-03-08')
61
+
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
43
70
  ```
44
71
 
45
72
  ### Treasury Bonds Data
46
73
  ```python
47
- # Get a DataFrame with the NTN-B data from ANBIMA
74
+ # Fetch a DataFrame with the NTN-B data from ANBIMA
48
75
  # Anbima data is available for the last 5 working days
49
76
  # Obs: Anbima members have access to the full history
50
- >>> yd.get_data(asset_code="NTN-B", reference_date='2024-04-12')
77
+ >>> yd.fetch_asset(asset_code="NTN-B", reference_date='2024-04-12')
51
78
 
52
79
  BondType ReferenceDate MaturityDate BidRate AskRate IndicativeRate Price
53
80
  NTN-B 2024-04-12 2024-08-15 0.07540 0.07504 0.07523 4,271.43565
@@ -74,29 +101,17 @@ NTN-F 2024-04-11 2033-01-01 -5.69000
74
101
  NTN-F 2024-04-11 2035-01-01 -1.27000
75
102
  ```
76
103
 
77
- ### Business Days Tools (Brazilian holidays are automatically considered)
104
+ ### Indicators Data
78
105
  ```python
79
- # Count the number of business days between two dates
80
- # Start date is included, end date is excluded
81
- >>> yd.count_bdays(start='2023-12-29', end='2024-01-02')
82
- 1
106
+ # Fetch the SELIC target rate from the Central Bank of Brazil
107
+ >>> yd.fetch_indicator(indicator_code="SELIC", reference_date='2024-04-12')
108
+ 10.75
83
109
 
84
- # Get the next business day after a given date (offset=1)
85
- >>> yd.offset_bdays(dates="2023-12-29", offset=1)
86
- Timestamp('2024-01-02 00:00:00')
110
+ # Fetch the IPCA monthly inflation rate from IBGE
111
+ >>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-03-18')
112
+ 0.16
87
113
 
88
- # Get the next business day if it is not a business day (offset=0)
89
- >>> yd.offset_bdays(dates="2023-12-30", offset=0)
90
- Timestamp('2024-01-02 00:00:00')
91
-
92
- # Since 2023-12-29 is a business day, it returns the same date (offset=0)
93
- >>> yd.offset_bdays(dates="2023-12-29", offset=0)
94
- Timestamp('2023-12-29 00:00:00')
95
-
96
- # Generate a pandas series with the business days between two dates
97
- >>> yd.generate_bdays(start='2023-12-29', end='2024-01-03')
98
- 0 2023-12-29
99
- 1 2024-01-02
100
- 2 2024-01-03
101
- dtype: datetime64[ns]
114
+ # If no data is yet available for the indicator, the function returns None
115
+ >>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-04-10')
116
+ None
102
117
  ```
@@ -0,0 +1 @@
1
+ __version__ = "0.7.1"
@@ -1,6 +1,6 @@
1
1
  from .__about__ import __version__
2
2
  from .bday import count_bdays, generate_bdays, is_bday, offset_bdays
3
- from .data_access import get_data
3
+ from .data_access import fetch_asset, fetch_indicator
4
4
  from .data_analysis import calculate_spreads
5
5
 
6
6
  __all__ = [
@@ -9,6 +9,7 @@ __all__ = [
9
9
  "generate_bdays",
10
10
  "offset_bdays",
11
11
  "count_bdays",
12
- "get_data",
12
+ "fetch_asset",
13
+ "fetch_indicator",
13
14
  "calculate_spreads",
14
15
  ]
@@ -0,0 +1,89 @@
1
+ import pandas as pd
2
+
3
+ from . import futures as ft
4
+ from . import indicators as it
5
+ from . import treasuries as tr
6
+ from .utils import _normalize_date
7
+
8
+
9
+ def fetch_asset(
10
+ asset_code: str,
11
+ reference_date: str | pd.Timestamp | None = None,
12
+ **kwargs,
13
+ ) -> pd.DataFrame:
14
+ """
15
+ Fetches data for a specified asset based on type and reference date.
16
+
17
+ Args:
18
+ asset_code (str): The asset code identifying the type of financial asset.
19
+ Supported options:
20
+ - "TRB": Treasury bonds (indicative rates from ANBIMA).
21
+ - "LTN", "LFT", "NTN-F", "NTN-B": Specific types of Brazilian treasury bonds
22
+ (indicative rates from ANBIMA).
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.
25
+ reference_date (str | pd.Timestamp | None): The reference date for which data is
26
+ fetched. Defaults to the previous business day if None.
27
+ **kwargs: Additional keyword arguments, specifically:
28
+ - return_raw (bool): Whether to return raw data without processing. Defaults
29
+ to False.
30
+
31
+ Returns:
32
+ pd.DataFrame: A DataFrame containing the fetched data for the specified asset.
33
+
34
+ Raises:
35
+ ValueError: If the asset code is not recognized or supported.
36
+
37
+ Examples:
38
+ >>> fetch_asset('TRB', '2023-04-01')
39
+ >>> fetch_asset('DI1', '2023-04-01', return_raw=True)
40
+ """
41
+ return_raw = kwargs.get("return_raw", False)
42
+ normalized_date = _normalize_date(reference_date)
43
+
44
+ if asset_code.lower() == "trb":
45
+ return tr.fetch_bonds(reference_date=normalized_date, return_raw=return_raw)
46
+ elif asset_code.lower() in ["ltn", "lft", "ntn-f", "ntn-b"]:
47
+ df = tr.fetch_bonds(reference_date=normalized_date)
48
+ return df.query(f"BondType == '{asset_code.upper()}'")
49
+
50
+ elif asset_code.lower() == "di1":
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)
54
+ else:
55
+ raise ValueError("Asset type not supported.")
56
+
57
+
58
+ def fetch_indicator(
59
+ indicator_code: str,
60
+ reference_date: str | pd.Timestamp | None = None,
61
+ ) -> float | None:
62
+ """
63
+ Fetches data for a specified economic indicator and reference date.
64
+
65
+ Args:
66
+ indicator_code (str): The code for the economic indicator. Supported options:
67
+ - "SELIC": SELIC target rate from the Central Bank of Brazil.
68
+ - "IPCA": IPCA monthly inflation rate from IBGE.
69
+ reference_date (str | pd.Timestamp | None): The reference date for which data is
70
+ fetched. Defaults to the previous business day if None.
71
+
72
+ Returns:
73
+ pd.Series: A Series containing the fetched data for the specified indicator.
74
+
75
+ Raises:
76
+ ValueError: If the indicator code is not recognized or supported.
77
+
78
+ Examples:
79
+ >>> fetch_indicator('SELIC', '2023-04-01')
80
+ >>> fetch_indicator('IPCA', '2023-04-01')
81
+ """
82
+ normalized_date = _normalize_date(reference_date)
83
+
84
+ if indicator_code.lower() == "selic":
85
+ return it.fetch_selic_target(reference_date=normalized_date)
86
+ elif indicator_code.lower() == "ipca":
87
+ return it.fetch_ipca_mr(reference_date=normalized_date)
88
+ else:
89
+ raise ValueError("Indicator type not supported.")
@@ -0,0 +1,10 @@
1
+ from .common import get_expiration_date, get_old_expiration_date
2
+ from .ddi import fetch_ddi
3
+ from .di import fetch_di
4
+
5
+ __all__ = [
6
+ "fetch_di",
7
+ "fetch_ddi",
8
+ "get_expiration_date",
9
+ "get_old_expiration_date",
10
+ ]
@@ -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