PYield 0.3.5__tar.gz → 0.3.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PYield
3
- Version: 0.3.5
3
+ Version: 0.3.7
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>
@@ -51,10 +51,11 @@ Description-Content-Type: text/markdown
51
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 Tesouro Nacional (TN), Banco Central (BC), ANBIMA, and B3.
52
52
  ## Features
53
53
 
54
- - Data Collection: Automated fetching of data from TN, BC, ANBIMA, and B3.
55
- - Data Processing: Efficient processing and normalization of fixed income data.
56
- - Analysis Tools: Built-in functions for common analysis tasks in fixed income markets.
57
- - Easy Integration: Seamless integration with Python data analysis workflows.
54
+ - **Data Collection**: Automated fetching of data from BC, ANBIMA and B3.
55
+ - **Data Processing**: Efficient processing and normalization of fixed income data.
56
+ - **Analysis Tools**: Built-in functions for common analysis tasks in fixed income markets.
57
+ - **Easy Integration**: Seamless integration with Python data analysis workflows.
58
+ - **Type Hints**: Full support for static type checking, enhancing development experience and code quality.
58
59
 
59
60
  ## Installation
60
61
 
@@ -67,17 +68,6 @@ pip install pyield
67
68
  ```python
68
69
  import pyield as yd
69
70
 
70
- # Get a pandas dataframe with the DI raw data from B3 (first date available is 05-06-1991)
71
- >>> yd.get_di(trade_date='2024-03-08', raw=True)
72
- VENCTO CONTR. ABERT.(1) CONTR. FECH.(2) NÚM. NEGOC. ... AJUSTE VAR. PTOS. ÚLT.OF. COMPRA ÚLT.OF. VENDA
73
- J24 3608626 3608626 294 ... 99,383.080 0,17- 10.952 10.956
74
- K24 607759 607759 179 ... 98,508.490 0,03+ 10.774 10.780
75
- M24 339641 339641 194 ... 97,706.210 1,68- 10.602 10.604
76
- ... ... ... ... ... ... ... ... ...
77
- F37 3517 3517 0 ... 26,863.980 336,32- 0.000 0.000
78
- F38 2200 2200 0 ... 24,262.320 327,41- 0.000 0.000
79
- F39 0 0 0 ... 21,894.700 317,01- 0.000 0.000
80
-
81
71
  # Get a pandas dataframe with the DI processed data from B3 (default)
82
72
  >>> yd.get_di(trade_date='2024-03-08')
83
73
  TradeDate ExpirationCode ExpirationDate BDToExpiration ... LastRate LastAskRate LastBidRate SettlementRate
@@ -9,10 +9,11 @@
9
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 Tesouro Nacional (TN), Banco Central (BC), ANBIMA, and B3.
10
10
  ## Features
11
11
 
12
- - Data Collection: Automated fetching of data from TN, BC, ANBIMA, and B3.
13
- - Data Processing: Efficient processing and normalization of fixed income data.
14
- - Analysis Tools: Built-in functions for common analysis tasks in fixed income markets.
15
- - Easy Integration: Seamless integration with Python data analysis workflows.
12
+ - **Data Collection**: Automated fetching of data from BC, ANBIMA and B3.
13
+ - **Data Processing**: Efficient processing and normalization of fixed income data.
14
+ - **Analysis Tools**: Built-in functions for common analysis tasks in fixed income markets.
15
+ - **Easy Integration**: Seamless integration with Python data analysis workflows.
16
+ - **Type Hints**: Full support for static type checking, enhancing development experience and code quality.
16
17
 
17
18
  ## Installation
18
19
 
@@ -25,17 +26,6 @@ pip install pyield
25
26
  ```python
26
27
  import pyield as yd
27
28
 
28
- # Get a pandas dataframe with the DI raw data from B3 (first date available is 05-06-1991)
29
- >>> yd.get_di(trade_date='2024-03-08', raw=True)
30
- VENCTO CONTR. ABERT.(1) CONTR. FECH.(2) NÚM. NEGOC. ... AJUSTE VAR. PTOS. ÚLT.OF. COMPRA ÚLT.OF. VENDA
31
- J24 3608626 3608626 294 ... 99,383.080 0,17- 10.952 10.956
32
- K24 607759 607759 179 ... 98,508.490 0,03+ 10.774 10.780
33
- M24 339641 339641 194 ... 97,706.210 1,68- 10.602 10.604
34
- ... ... ... ... ... ... ... ... ...
35
- F37 3517 3517 0 ... 26,863.980 336,32- 0.000 0.000
36
- F38 2200 2200 0 ... 24,262.320 327,41- 0.000 0.000
37
- F39 0 0 0 ... 21,894.700 317,01- 0.000 0.000
38
-
39
29
  # Get a pandas dataframe with the DI processed data from B3 (default)
40
30
  >>> yd.get_di(trade_date='2024-03-08')
41
31
  TradeDate ExpirationCode ExpirationDate BDToExpiration ... LastRate LastAskRate LastBidRate SettlementRate
@@ -0,0 +1 @@
1
+ __version__ = "0.3.7"
@@ -0,0 +1,15 @@
1
+ from .__about__ import __version__
2
+ from .di_futures import get_di, read_di
3
+ from .br_calendar import count_bdays, generate_bdays, offset_bdays
4
+ from .anbima import get_treasury_rates, calculate_treasury_di_spreads
5
+
6
+ __all__ = [
7
+ "__version__",
8
+ "get_di",
9
+ "read_di",
10
+ "count_bdays",
11
+ "generate_bdays",
12
+ "offset_bdays",
13
+ "get_treasury_rates",
14
+ "calculate_treasury_di_spreads",
15
+ ]
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+ from pandas import Timestamp, DataFrame
2
3
  from urllib.error import HTTPError
3
4
 
4
5
  from . import di_futures as di
@@ -12,9 +13,7 @@ ANBIMA_MEMBER_URL = "http://www.anbima.associados.rtm/merc_sec/arqs/"
12
13
  BP_CONVERSION_FACTOR = 10_000
13
14
 
14
15
 
15
- def process_reference_date(
16
- reference_date: str | pd.Timestamp | None = None,
17
- ) -> pd.Timestamp:
16
+ def process_reference_date(reference_date: str | Timestamp | None = None) -> Timestamp:
18
17
  """
19
18
  Process the given reference date, converting it to a pandas Timestamp. If no date is provided,
20
19
  use the previous business day based on the Brazilian calendar.
@@ -26,17 +25,25 @@ def process_reference_date(
26
25
  - pd.Timestamp: The processed reference date.
27
26
  """
28
27
  if reference_date: # Force reference_date to be a pd.Timestamp
29
- processed_date = pd.Timestamp(reference_date)
28
+ processed_date = pd.to_datetime(reference_date)
30
29
  else: # If no reference_date is given, use the previous business day
31
- today = pd.Timestamp.today().date()
32
- processed_date = cl.offset_bdays(today, -1)
30
+ today = pd.Timestamp.today().normalize()
31
+ processed_date = cl.offset_bdays(today, -1) # type: ignore
32
+
33
+ # Check if the reference date is Timestamp
34
+ if not isinstance(processed_date, Timestamp):
35
+ raise ValueError("Reference date must be a pandas Timestamp.")
36
+
37
+ # Raise an error if the reference date is in the future
38
+ if processed_date > pd.Timestamp.today().normalize():
39
+ raise ValueError("Reference date cannot be in the future.")
33
40
 
34
41
  return processed_date
35
42
 
36
43
 
37
44
  def get_raw_data(
38
- reference_date: str | pd.Timestamp | None = None, is_anbima_member: bool = False
39
- ) -> pd.DataFrame:
45
+ reference_date: str | Timestamp | None = None, is_anbima_member: bool = False
46
+ ) -> DataFrame:
40
47
  """
41
48
  Fetch indicative rates from ANBIMA for a specific date.
42
49
 
@@ -75,7 +82,7 @@ def get_raw_data(
75
82
  return df
76
83
 
77
84
 
78
- def process_raw_data(df: pd.DataFrame) -> pd.DataFrame:
85
+ def process_raw_data(df_raw: DataFrame) -> DataFrame:
79
86
  """
80
87
  Process raw data from ANBIMA by filtering selected columns, renaming them, and adjusting data formats.
81
88
 
@@ -104,8 +111,8 @@ def process_raw_data(df: pd.DataFrame) -> pd.DataFrame:
104
111
  # "Criterio": "Criteria",
105
112
  }
106
113
  select_columns = list(selected_columns_dict.keys())
107
- df = df[select_columns].copy()
108
- df.rename(columns=selected_columns_dict, inplace=True)
114
+ df = df_raw[select_columns].copy()
115
+ df = df.rename(columns=selected_columns_dict)
109
116
 
110
117
  # Remove percentage from rates
111
118
  rate_cols = ["BidRate", "AskRate", "IndicativeRate"]
@@ -118,10 +125,10 @@ def process_raw_data(df: pd.DataFrame) -> pd.DataFrame:
118
125
 
119
126
 
120
127
  def get_treasury_rates(
121
- reference_date: str | pd.Timestamp | None = None,
128
+ reference_date: str | Timestamp | None = None,
122
129
  return_raw=False,
123
130
  is_anbima_member=False,
124
- ) -> pd.DataFrame:
131
+ ) -> DataFrame:
125
132
  """
126
133
  Fetch and process indicative rates from ANBIMA for a specific date.
127
134
  If no date is provided, the previous business day based on the Brazilian calendar
@@ -146,9 +153,9 @@ def get_treasury_rates(
146
153
 
147
154
 
148
155
  def calculate_treasury_di_spreads(
149
- reference_date: str | pd.Timestamp | None = None,
156
+ reference_date: str | Timestamp | None = None,
150
157
  is_anbima_member=False,
151
- ) -> pd.DataFrame:
158
+ ) -> DataFrame:
152
159
  """
153
160
  Calculate the DI spread for LTN and NTN-F bonds based on ANBIMA's indicative rates.
154
161
  If no date is provided, the previous business day based on the Brazilian calendar
@@ -1,17 +1,37 @@
1
- from typing import Literal
1
+ from typing import Literal, overload
2
2
 
3
- import pandas as pd
4
3
  import numpy as np
4
+ import pandas as pd
5
+ from pandas import Series, Timestamp
5
6
 
6
- from .br_holidays import BRHolidays
7
+ from .br_holidays import BrHolidays
7
8
 
8
9
  # Instância global da classe Holidays
9
- br_holidays = BRHolidays()
10
+ br_holidays = BrHolidays()
11
+
12
+
13
+ @overload
14
+ def format_input_dates(dates: str | Timestamp | None) -> Timestamp: ...
15
+
16
+
17
+ @overload
18
+ def format_input_dates(dates: Series) -> Series: ...
19
+
20
+
21
+ # Implementação da função que atende às sobrecargas acima
22
+ def format_input_dates(dates: str | Timestamp | Series | None) -> Timestamp | Series:
23
+ if dates is None:
24
+ return pd.Timestamp.today().normalize()
25
+ result = pd.to_datetime(dates)
26
+ if isinstance(result, pd.Series):
27
+ return result
28
+ elif result is pd.NaT:
29
+ raise ValueError("Invalid date format.")
30
+ else:
31
+ return pd.Timestamp(result).normalize()
10
32
 
11
33
 
12
- def convert_to_numpy_date(
13
- dates: str | pd.Timestamp | pd.Series,
14
- ) -> np.datetime64 | np.ndarray:
34
+ def convert_to_numpy_date(dates: Timestamp | Series) -> np.datetime64 | np.ndarray:
15
35
  """
16
36
  Converts the input dates to a numpy datetime64[D] format.
17
37
 
@@ -21,20 +41,33 @@ def convert_to_numpy_date(
21
41
  Returns:
22
42
  np.datetime64 | np.ndarray: The input dates in a numpy datetime64[D] format.
23
43
  """
24
- # Assure that the input is in pandas datetime64[ns] format
25
- dates_pd = pd.to_datetime(dates)
26
-
27
- if isinstance(dates_pd, pd.Timestamp):
28
- return np.datetime64(dates_pd, "D")
44
+ if isinstance(dates, pd.Timestamp):
45
+ return np.datetime64(dates, "D")
29
46
  else:
30
- return dates_pd.to_numpy().astype("datetime64[D]")
47
+ return dates.to_numpy().astype("datetime64[D]")
48
+
49
+
50
+ @overload
51
+ def offset_bdays(
52
+ dates: str | Timestamp | None = None,
53
+ offset: int = 0,
54
+ holiday_list: Literal["old", "new", "infer"] = "infer",
55
+ ) -> Timestamp: ...
56
+
57
+
58
+ @overload
59
+ def offset_bdays(
60
+ dates: Series,
61
+ offset: int = 0,
62
+ holiday_list: Literal["old", "new", "infer"] = "infer",
63
+ ) -> Series: ...
31
64
 
32
65
 
33
66
  def offset_bdays(
34
- dates: str | pd.Timestamp | pd.Series,
35
- offset: int,
67
+ dates: str | Timestamp | Series | None = None,
68
+ offset: int = 0,
36
69
  holiday_list: Literal["old", "new", "infer"] = "infer",
37
- ):
70
+ ) -> Timestamp | Series:
38
71
  """
39
72
  Offsets the dates to the next or previous business day. This function is a wrapper
40
73
  for `numpy.busday_offset` to be used directly with Pandas data types that infers the
@@ -68,20 +101,53 @@ def offset_bdays(
68
101
  >>> yd.offset_bdays(date, -1)
69
102
  Timestamp('2023-12-21') # Offset to the previous business day
70
103
  """
71
- dates_np = convert_to_numpy_date(dates)
104
+ formatted_dates = format_input_dates(dates)
72
105
 
73
- selected_holidays = br_holidays.get_applicable_holidays(dates_np, holiday_list)
106
+ selected_holidays = br_holidays.get_applicable_holidays(
107
+ formatted_dates, holiday_list
108
+ )
109
+ selected_holidays_np = convert_to_numpy_date(selected_holidays)
74
110
 
75
- # Offset the dates
76
- offsetted_dates = np.busday_offset(
77
- dates_np, offsets=offset, roll="forward", holidays=selected_holidays
111
+ dates_np = convert_to_numpy_date(formatted_dates)
112
+ offsetted_dates_np = np.busday_offset(
113
+ dates_np, offsets=offset, roll="forward", holidays=selected_holidays_np
78
114
  )
115
+ if isinstance(offsetted_dates_np, np.datetime64):
116
+ return pd.Timestamp(offsetted_dates_np, unit="ns")
117
+ else:
118
+ result = pd.to_datetime(offsetted_dates_np, unit="ns")
119
+ return pd.Series(result)
120
+
121
+
122
+ @overload
123
+ def count_bdays(
124
+ start: str | Timestamp | None = None,
125
+ end: str | Timestamp | None = None,
126
+ holiday_list: Literal["old", "new", "infer"] = "infer",
127
+ ) -> int: ...
79
128
 
80
- # Convert the dates back to a pandas datetime64[ns] format
81
- return pd.to_datetime(offsetted_dates, unit="ns")
82
129
 
130
+ @overload
131
+ def count_bdays(
132
+ start: Series,
133
+ end: str | Timestamp | Series | None,
134
+ holiday_list: Literal["old", "new", "infer"] = "infer",
135
+ ) -> Series: ...
136
+
137
+
138
+ @overload
139
+ def count_bdays(
140
+ start: str | Timestamp | Series | None,
141
+ end: Series,
142
+ holiday_list: Literal["old", "new", "infer"] = "infer",
143
+ ) -> Series: ...
83
144
 
84
- def count_bdays(start, end, holiday_list: Literal["old", "new", "infer"] = "infer"):
145
+
146
+ def count_bdays(
147
+ start: str | Timestamp | Series | None = None,
148
+ end: str | Timestamp | Series | None = None,
149
+ holiday_list: Literal["old", "new", "infer"] = "infer",
150
+ ) -> int | Series:
85
151
  """
86
152
  Counts the number of business days between a `start` (inclusive) and `end`
87
153
  (exclusive). If an end date is earlier than the start date, the count will be
@@ -89,16 +155,15 @@ def count_bdays(start, end, holiday_list: Literal["old", "new", "infer"] = "infe
89
155
  with Pandas data types.
90
156
 
91
157
  Args:
92
- start (str | pd.Timestamp, optional): The start date. Defaults to None.
93
- end (str | pd.Timestamp optional): The end date. Defaults to None.
158
+ start (str | Timestamp, optional): The start date. Defaults to None.
159
+ end (str | Timestamp optional): The end date. Defaults to None.
94
160
  holiday_list (str, optional): The list of holidays to use. Defaults to "infer",
95
161
  which infers the right list of holidays based on the most recent date in
96
162
  the input.
97
163
 
98
164
  Returns:
99
- np.int64 | np.ndarray: The number of business days between the start date and
100
- end dates. Returns a single integer if `end` is a single Timestamp, otherwise
101
- returns an ndarray of integers.
165
+ int | pd.Series: The number of business days between the start date and end date.
166
+ Returns an integer if the result is a single value, otherwise returns a Series.
102
167
 
103
168
  Notes:
104
169
  - For more information on error handling, see numpy.busday_count documentation at
@@ -116,25 +181,36 @@ def count_bdays(start, end, holiday_list: Literal["old", "new", "infer"] = "infe
116
181
  >>> start = '2023-01-01'
117
182
  >>> end = pd.to_datetime(['2023-01-31', '2023-03-01'])
118
183
  >>> yd.count_bdays(start, end)
119
- array([22, 40])
184
+ pd.Series([22, 40], dtype='int64')
120
185
  """
121
- # Convert inputs to a Series of datetime64[ns] even if a single value was passed
122
- start_np = convert_to_numpy_date(start)
123
- end_np = convert_to_numpy_date(end)
186
+ formatted_start = format_input_dates(start)
187
+ formatted_end = format_input_dates(end)
124
188
 
125
189
  # Determine which list of holidays to use
126
- selected_holidays = br_holidays.get_applicable_holidays(start_np, holiday_list)
190
+ selected_holidays = br_holidays.get_applicable_holidays(
191
+ formatted_start, holiday_list
192
+ )
193
+ selected_holidays_np = convert_to_numpy_date(selected_holidays)
127
194
 
128
- return np.busday_count(start_np, end_np, holidays=selected_holidays)
195
+ # Convert inputs to numpy datetime64[D] before calling numpy.busday_count
196
+ start_np = convert_to_numpy_date(formatted_start)
197
+ end_np = convert_to_numpy_date(formatted_end)
198
+
199
+ result_np = np.busday_count(start_np, end_np, holidays=selected_holidays_np)
200
+ if isinstance(result_np, np.ndarray):
201
+ # Return pandas Int64 type for type consistency
202
+ return pd.Series(result_np, dtype="Int64")
203
+ else:
204
+ return int(result_np)
129
205
 
130
206
 
131
207
  def generate_bdays(
132
- start=None,
133
- end=None,
134
- inclusive="both",
208
+ start: str | Timestamp | None = None,
209
+ end: str | Timestamp | None = None,
210
+ inclusive: Literal["both", "neither", "left", "right"] = "both",
135
211
  holiday_list: Literal["old", "new", "infer"] = "infer",
136
212
  **kwargs,
137
- ) -> pd.DatetimeIndex:
213
+ ) -> Series:
138
214
  """
139
215
  Generates a Series of business days between a `start` (inclusive) and `end`
140
216
  (inclusive) that takes into account the list of brazilian holidays as the default.
@@ -175,15 +251,23 @@ def generate_bdays(
175
251
  2023-12-29 2023-12-29
176
252
  dtype: object
177
253
  """
178
- if start is None:
179
- start = pd.Timestamp.today()
254
+ formatted_start = format_input_dates(start)
255
+ formatted_end = format_input_dates(end)
180
256
 
181
- if end is None:
182
- end = pd.Timestamp.today()
257
+ if isinstance(formatted_start, Series) or isinstance(formatted_end, Series):
258
+ raise ValueError("The start and end dates must be single dates.")
183
259
 
184
- start_np = convert_to_numpy_date(start)
185
- selected_holidays = br_holidays.get_applicable_holidays(start_np, holiday_list)
186
-
187
- return pd.bdate_range(
188
- start, end, freq="C", inclusive=inclusive, holidays=selected_holidays, **kwargs
260
+ selected_holidays = br_holidays.get_applicable_holidays(
261
+ formatted_start, holiday_list
262
+ )
263
+ selected_holidays_list = selected_holidays.to_list()
264
+
265
+ result = pd.bdate_range(
266
+ formatted_start,
267
+ formatted_end,
268
+ freq="C",
269
+ inclusive=inclusive,
270
+ holidays=selected_holidays_list,
271
+ **kwargs,
189
272
  )
273
+ return pd.Series(result)
@@ -2,12 +2,12 @@ from pathlib import Path
2
2
  from typing import Literal
3
3
 
4
4
  import pandas as pd
5
- import numpy as np
5
+ from pandas import Series, Timestamp
6
6
 
7
7
 
8
- class BRHolidays:
8
+ class BrHolidays:
9
9
  # The date (inclusive) when the new holidays list starts to be valid
10
- TRANSITION_DATE = np.datetime64("2023-12-26", "D")
10
+ TRANSITION_DATE = pd.to_datetime("2023-12-26")
11
11
 
12
12
  def __init__(self):
13
13
  current_dir = Path(__file__).parent
@@ -16,34 +16,33 @@ class BRHolidays:
16
16
  self.new_holidays = self._load_holidays(new_holidays_path)
17
17
  self.old_holidays = self._load_holidays(old_holidays_path)
18
18
 
19
- def _load_holidays(self, file_path: Path) -> np.array:
19
+ def _load_holidays(self, file_path: Path) -> Series:
20
+ """Loads the list of holidays from a text file and returns it as a pd.Series of pd.Timestamp."""
20
21
  df = pd.read_csv(file_path, header=None, names=["date"], comment="#")
21
- holiday_dates = pd.to_datetime(df["date"], format="%d/%m/%Y")
22
- # Return the dates as a numpy array of datetime64[D]
23
- return holiday_dates.values.astype("datetime64[D]")
22
+ # Convert the dates to a pd.Series of pd.Timestamp
23
+ return pd.to_datetime(df["date"], format="%d/%m/%Y")
24
24
 
25
25
  def get_applicable_holidays(
26
26
  self,
27
- dates: np.datetime64 | np.ndarray,
27
+ dates: Timestamp | Series,
28
28
  holiday_list: Literal["old", "new", "infer"] = "infer",
29
- ) -> np.array:
29
+ ) -> Series:
30
30
  """
31
31
  Returns the correct list of holidays to use based on the most recent date in the input.
32
32
 
33
33
  Args:
34
- dates (np.datetime64 | np.ndarray): The date(s) to use to determine the list of
35
- holidays.
34
+ dates (pd.Timestamp | pd.Series): The date(s) to use to infer the holidays.
36
35
  holiday_list (str): The holidays list to use. Valid options are 'old', 'new' or
37
36
  'infer'. If 'infer' is used, the list of holidays is selected based on the
38
37
  earliest (minimum) date in the input.
39
38
 
40
39
  Returns:
41
- np.array: The list of holidays to use.
40
+ pd.Series: The list of holidays to use.
42
41
  """
43
- if isinstance(dates, np.datetime64):
42
+ if isinstance(dates, Timestamp):
44
43
  earliest_date = dates
45
44
  else:
46
- earliest_date = np.min(dates)
45
+ earliest_date = dates.min()
47
46
 
48
47
  match holiday_list:
49
48
  case "old":
@@ -51,7 +50,7 @@ class BRHolidays:
51
50
  case "new":
52
51
  return self.new_holidays
53
52
  case "infer":
54
- if earliest_date < BRHolidays.TRANSITION_DATE:
53
+ if earliest_date < BrHolidays.TRANSITION_DATE:
55
54
  return self.old_holidays
56
55
  else:
57
56
  return self.new_holidays
@@ -1,13 +1,15 @@
1
1
  from typing import Literal
2
2
  from pathlib import Path
3
+
3
4
  import pandas as pd
5
+ from pandas import DataFrame, Timestamp
4
6
 
5
7
  from . import di_web as diw
6
8
  from . import di_xml as dix
7
9
  from . import br_calendar as brc
8
10
 
9
11
 
10
- def get_expiration_date(expiration_code: str) -> pd.Timestamp:
12
+ def get_expiration_date(expiration_code: str) -> Timestamp:
11
13
  """
12
14
  Internal function to convert the expiration code into its expiration date.
13
15
 
@@ -62,15 +64,18 @@ def get_expiration_date(expiration_code: str) -> pd.Timestamp:
62
64
  year = int("20" + expiration_code[-2:])
63
65
  # The expiration date is always the first business day of the month
64
66
  expiration = pd.Timestamp(year, month, 1)
67
+
65
68
  # Adjust to the next business day when expiration date is a weekend or a holiday
66
- return brc.offset_bdays(expiration, offset=0)
69
+ adj_expiration = brc.offset_bdays(expiration, offset=0)
70
+
71
+ return adj_expiration
67
72
 
68
73
  except (KeyError, ValueError):
69
- return pd.NaT
74
+ raise ValueError("Invalid expiration code.")
70
75
 
71
76
 
72
77
  def get_di(
73
- trade_date: str | pd.Timestamp,
78
+ trade_date: str | Timestamp,
74
79
  source_type: Literal["bmf", "b3", "b3s"] = "bmf",
75
80
  return_raw: bool = False,
76
81
  ) -> pd.DataFrame:
@@ -120,7 +125,7 @@ def get_di(
120
125
  raise ValueError("source_type must be either 'bmf', 'b3' or 'b3s'.")
121
126
 
122
127
 
123
- def read_di(file_path: Path, return_raw: bool = False) -> pd.DataFrame:
128
+ def read_di(file_path: Path, return_raw: bool = False) -> DataFrame:
124
129
  """
125
130
  Reads a DI futures data file and returns a DataFrame.
126
131
 
@@ -1,7 +1,9 @@
1
1
  import io
2
2
  import warnings
3
+ from typing import Optional
3
4
 
4
5
  import pandas as pd
6
+ from pandas import Timestamp, Series, DataFrame
5
7
  import requests
6
8
 
7
9
  from . import br_calendar as brc
@@ -9,8 +11,8 @@ from . import di_futures as dif
9
11
 
10
12
 
11
13
  def get_old_expiration_date(
12
- ExpirationCode: str, trade_date: pd.Timestamp
13
- ) -> pd.Timestamp:
14
+ ExpirationCode: str, trade_date: Timestamp
15
+ ) -> Optional[Timestamp]:
14
16
  """
15
17
  Internal function to convert an old DI contract code into its ExpirationDate date. Valid for
16
18
  contract codes up to 21-05-2006.
@@ -67,10 +69,10 @@ def get_old_expiration_date(
67
69
  return brc.offset_bdays(ExpirationDate, offset=0, holiday_list="old")
68
70
 
69
71
  except (KeyError, ValueError):
70
- return pd.NaT
72
+ return pd.NaT # type: ignore
71
73
 
72
74
 
73
- def get_raw_di(trade_date: pd.Timestamp) -> pd.DataFrame:
75
+ def get_raw_di(trade_date: Timestamp) -> DataFrame:
74
76
  """
75
77
  Internal function to fetch raw DI futures data from B3 for a specific trade date.
76
78
 
@@ -118,7 +120,7 @@ def get_raw_di(trade_date: pd.Timestamp) -> pd.DataFrame:
118
120
  return pd.DataFrame()
119
121
 
120
122
 
121
- def convert_prices_to_rates(prices: pd.Series, bd: pd.Series) -> pd.Series:
123
+ def convert_prices_to_rates(prices: Series, bd: Series) -> Series:
122
124
  """
123
125
  Internal function to convert DI futures prices to rates.
124
126
 
@@ -135,7 +137,7 @@ def convert_prices_to_rates(prices: pd.Series, bd: pd.Series) -> pd.Series:
135
137
  return 100 * rates
136
138
 
137
139
 
138
- def convert_prices_in_older_contracts(df: pd.DataFrame) -> pd.DataFrame:
140
+ def convert_prices_in_older_contracts(df: DataFrame) -> DataFrame:
139
141
  # Prior to 01/01/2002, prices were not converted to rates
140
142
  convert_cols = [
141
143
  "FirstRate",
@@ -155,7 +157,7 @@ def convert_prices_in_older_contracts(df: pd.DataFrame) -> pd.DataFrame:
155
157
  return df
156
158
 
157
159
 
158
- def process_di(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
160
+ def process_di(df: DataFrame, trade_date: Timestamp) -> DataFrame:
159
161
  """
160
162
  Internal function to process and transform raw DI futures data.
161
163
 
@@ -264,7 +266,7 @@ def process_di(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
264
266
  return df[ordered_cols]
265
267
 
266
268
 
267
- def get_di(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
269
+ def get_di(trade_date: Timestamp, return_raw: bool = False) -> DataFrame:
268
270
  """
269
271
  Gets the DI futures data for a given date from B3.
270
272
 
@@ -4,13 +4,14 @@ from pathlib import Path
4
4
 
5
5
  import requests
6
6
  import pandas as pd
7
+ from pandas import Timestamp, DataFrame
7
8
  from lxml import etree
8
9
 
9
10
  from . import di_futures as dif
10
11
  from . import br_calendar as brc
11
12
 
12
13
 
13
- def get_file_from_url(trade_date: pd.Timestamp, source_type: str) -> io.BytesIO:
14
+ def get_file_from_url(trade_date: Timestamp, source_type: str) -> io.BytesIO:
14
15
  """
15
16
  Types of XML files available:
16
17
  Full Price Report (all assets)
@@ -57,7 +58,7 @@ def extract_xml_from_zip(zip_file: io.BytesIO) -> io.BytesIO:
57
58
  return io.BytesIO(inner_file_content)
58
59
 
59
60
 
60
- def extract_di_data_from_xml(xml_file: io.BytesIO) -> list:
61
+ def extract_di_data_from_xml(xml_file: io.BytesIO) -> list[dict]:
61
62
  parser = etree.XMLParser(
62
63
  ns_clean=True, remove_blank_text=True, remove_comments=True, recover=True
63
64
  )
@@ -69,18 +70,38 @@ def extract_di_data_from_xml(xml_file: io.BytesIO) -> list:
69
70
  '//ns:TckrSymb[starts-with(text(), "DI1")]', namespaces=namespaces
70
71
  )
71
72
 
72
- # Lista para armazenar os dados
73
+ if (
74
+ tckr_symbols is None
75
+ or not isinstance(tckr_symbols, list)
76
+ or len(tckr_symbols) == 0
77
+ ):
78
+ return []
79
+
80
+ # Lista para armazenar os dados com type hinting
81
+ # di_data: list[dict] = []
73
82
  di_data = []
74
83
 
75
84
  # Processar cada TckrSymb encontrado
76
85
  for tckr_symb in tckr_symbols:
77
- # Acessar o elemento PricRpt que contém o TckrSymb
78
- price_report = tckr_symb.getparent().getparent()
86
+ if isinstance(tckr_symb, etree._Element):
87
+ price_report = tckr_symb.getparent()
88
+ if price_report is not None:
89
+ price_report = price_report.getparent()
90
+ else:
91
+ # Handle the case where tckr_symb doesn't have a parent
92
+ continue
93
+ else:
94
+ # Handle the case where tckr_symb is not an _Element
95
+ continue
79
96
 
80
97
  # Extrair a data de negociação
98
+ if price_report is None:
99
+ continue
81
100
  trade_date = price_report.find(".//ns:TradDt/ns:Dt", namespaces)
82
101
 
83
102
  # Preparar o dicionário de dados do ticker com a data de negociação
103
+ if trade_date is None:
104
+ continue
84
105
  ticker_data = {"TradDt": trade_date.text, "TckrSymb": tckr_symb.text}
85
106
 
86
107
  # Acessar o elemento FinInstrmAttrbts que contém o TckrSymb
@@ -99,7 +120,7 @@ def extract_di_data_from_xml(xml_file: io.BytesIO) -> list:
99
120
  return di_data
100
121
 
101
122
 
102
- def create_df_from_di_data(di1_data: list) -> pd.DataFrame:
123
+ def create_df_from_di_data(di1_data: list) -> DataFrame:
103
124
  # Criar um DataFrame com os dados coletados
104
125
  df = pd.DataFrame(di1_data)
105
126
 
@@ -108,7 +129,7 @@ def create_df_from_di_data(di1_data: list) -> pd.DataFrame:
108
129
  return pd.read_csv(file, dtype_backend="numpy_nullable")
109
130
 
110
131
 
111
- def filter_and_order_pr_df(df: pd.DataFrame) -> pd.DataFrame:
132
+ def filter_and_order_pr_df(df: DataFrame) -> DataFrame:
112
133
  selected_columns = [
113
134
  "TradDt",
114
135
  "TckrSymb",
@@ -144,7 +165,7 @@ def filter_and_order_pr_df(df: pd.DataFrame) -> pd.DataFrame:
144
165
  return df[selected_columns]
145
166
 
146
167
 
147
- def filter_and_order_sprd_df(df: pd.DataFrame) -> pd.DataFrame:
168
+ def filter_and_order_sprd_df(df: DataFrame) -> DataFrame:
148
169
  cols = [
149
170
  "TradDt",
150
171
  "TckrSymb",
@@ -166,7 +187,7 @@ def filter_and_order_sprd_df(df: pd.DataFrame) -> pd.DataFrame:
166
187
  return df[cols]
167
188
 
168
189
 
169
- def standardize_column_names(df: pd.DataFrame) -> pd.DataFrame:
190
+ def standardize_column_names(df: DataFrame) -> DataFrame:
170
191
  rename_dict = {
171
192
  "TradDt": "TradeDate",
172
193
  "TckrSymb": "Ticker",
@@ -203,7 +224,7 @@ def standardize_column_names(df: pd.DataFrame) -> pd.DataFrame:
203
224
  return df.rename(columns=rename_dict)
204
225
 
205
226
 
206
- def process_di_df(df_raw: pd.DataFrame) -> pd.DataFrame:
227
+ def process_di_df(df_raw: DataFrame) -> DataFrame:
207
228
  df = df_raw.copy()
208
229
  # Convert to datetime64[ns] since it is pandas default type for timestamps
209
230
  df["TradDt"] = df["TradDt"].astype("datetime64[ns]")
@@ -223,11 +244,7 @@ def process_di_df(df_raw: pd.DataFrame) -> pd.DataFrame:
223
244
  return df.sort_values(by=["ExpirationDate"], ignore_index=True)
224
245
 
225
246
 
226
- def get_di(
227
- trade_date: pd.Timestamp,
228
- source_type: str,
229
- return_raw: bool,
230
- ) -> pd.DataFrame:
247
+ def get_di(trade_date: Timestamp, source_type: str, return_raw: bool) -> DataFrame:
231
248
  zip_file = get_file_from_url(trade_date, source_type)
232
249
 
233
250
  xml_file = extract_xml_from_zip(zip_file)
@@ -243,6 +260,8 @@ def get_di(
243
260
  df_di = filter_and_order_pr_df(df_raw)
244
261
  elif source_type == "b3s":
245
262
  df_di = filter_and_order_sprd_df(df_raw)
263
+ else:
264
+ raise ValueError("Invalid source type. Must be 'b3' or 'b3s'.")
246
265
 
247
266
  # Process and transform data
248
267
  df_di = process_di_df(df_di)
@@ -253,7 +272,7 @@ def get_di(
253
272
  return df_di
254
273
 
255
274
 
256
- def read_di(file_path: Path, return_raw: bool = False) -> pd.DataFrame:
275
+ def read_di(file_path: Path, return_raw: bool = False) -> DataFrame:
257
276
  if file_path:
258
277
  if file_path.exists():
259
278
  content = file_path.read_bytes()
@@ -31,7 +31,7 @@ dependencies = [
31
31
  "lxml",
32
32
  ]
33
33
  dynamic = []
34
- version = "0.3.5"
34
+ version = "0.3.7"
35
35
 
36
36
  [project.license]
37
37
  file = "LICENSE"
@@ -50,4 +50,5 @@ path = "pyield/__about__.py"
50
50
  dev = [
51
51
  "ipykernel",
52
52
  "pytest",
53
+ "mypy",
53
54
  ]
File without changes
@@ -1 +0,0 @@
1
- __version__ = "0.3.5"
@@ -1,4 +0,0 @@
1
- from .__about__ import __version__ # noqa: F401
2
- from .di_futures import get_di, read_di # noqa: F401
3
- from .br_calendar import count_bdays, generate_bdays, offset_bdays # noqa: F401
4
- from .anbima import get_treasury_rates, calculate_treasury_di_spreads # noqa: F401
File without changes
File without changes
File without changes