PYield 0.4.0__tar.gz → 0.5.0__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.4.0
3
+ Version: 0.5.0
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>
@@ -0,0 +1 @@
1
+ __version__ = "0.5.0"
@@ -1,6 +1,6 @@
1
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, is_business_day
2
+ from .di import get_di, read_di
3
+ from .calendar import count_bdays, generate_bdays, offset_bdays, is_business_day
4
4
  from .anbima import get_treasury_rates, calculate_treasury_di_spreads
5
5
 
6
6
  __all__ = [
@@ -1,25 +1,29 @@
1
+ import io
2
+
1
3
  import pandas as pd
4
+ import requests
5
+
2
6
  from pandas import Timestamp, DataFrame
3
- from urllib.error import HTTPError
4
7
 
5
- from . import di_futures as di
6
- from . import br_calendar as cl
8
+ from . import di
9
+ from . import calendar as cl
7
10
 
8
11
  # URL Constants
9
12
  ANBIMA_NON_MEMBER_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs/"
10
13
  ANBIMA_MEMBER_URL = "http://www.anbima.associados.rtm/merc_sec/arqs/"
11
14
 
12
15
  # Constant for conversion to basis points
13
- BP_CONVERSION_FACTOR = 10_000
16
+ BPS_CONVERSION_FACTOR = 10_000
14
17
 
15
18
 
16
- def normalize_date(reference_date: str | Timestamp | None = None) -> Timestamp:
19
+ def _normalize_date(reference_date: str | Timestamp | None = None) -> Timestamp:
17
20
  if isinstance(reference_date, str):
18
21
  normalized_date = pd.Timestamp(reference_date).normalize()
19
22
  elif isinstance(reference_date, Timestamp):
20
23
  normalized_date = reference_date.normalize()
21
24
  elif reference_date is None:
22
25
  today = pd.Timestamp.today().normalize()
26
+ # Get last business day before today
23
27
  normalized_date = cl.offset_bdays(today, -1)
24
28
  else:
25
29
  raise ValueError("Invalid date format.")
@@ -35,48 +39,51 @@ def normalize_date(reference_date: str | Timestamp | None = None) -> Timestamp:
35
39
  return normalized_date
36
40
 
37
41
 
38
- def get_raw_data(
39
- reference_date: str | Timestamp | None = None, is_anbima_member: bool = False
40
- ) -> DataFrame:
41
- """
42
- Fetch indicative rates from ANBIMA for a specific date.
43
-
44
- Parameters:
45
- - reference_date (pd.Timestamp): Date for which to fetch the indicative rates.
46
- - is_anbima_member (bool): Whether the request is being made by an ANBIMA member.
47
-
48
- Returns:
49
- - pd.DataFrame: DataFrame with the indicative rates for the given date.
50
- """
51
- # Process the reference date, defaulting to the previous business day if not provided
52
- normalized_date = normalize_date(reference_date)
53
-
54
- # Format the date to match the URL format
55
- url_date = normalized_date.strftime("%y%m%d")
56
-
57
- # Set the base URL according to the member status
58
- base_url = ANBIMA_MEMBER_URL if is_anbima_member else ANBIMA_NON_MEMBER_URL
59
- # url example: https://www.anbima.com.br/informacoes/merc-sec/arqs/ms231128.txt
60
- url = f"{base_url}ms{url_date}.txt"
42
+ def _get_anbima_content(reference_date: Timestamp) -> str:
43
+ url_date = reference_date.strftime("%y%m%d")
44
+ member_url = f"{ANBIMA_MEMBER_URL}ms{url_date}.txt"
45
+ non_member_url = f"{ANBIMA_NON_MEMBER_URL}ms{url_date}.txt"
61
46
 
47
+ # Tries to access the member URL first
62
48
  try:
63
- df = pd.read_csv(
64
- url,
65
- sep="@",
66
- encoding="latin-1",
67
- skiprows=2,
68
- decimal=",",
69
- thousands=".",
70
- dtype_backend="numpy_nullable",
71
- )
72
- except HTTPError:
73
- error_date = normalized_date.strftime("%d-%m-%Y")
74
- raise ValueError(f"Failed to get ANBIMA rates for {error_date}")
75
-
49
+ response = requests.get(member_url)
50
+ # Checks if the response was successful (status code 200)
51
+ response.raise_for_status()
52
+ return response.text
53
+ except requests.exceptions.RequestException:
54
+ # Blind attempt to access the member URL
55
+ pass
56
+
57
+ # If the member URL fails, tries to access the non-member URL
58
+ try:
59
+ response = requests.get(non_member_url)
60
+ response.raise_for_status() # Checks if the second attempt was successful
61
+ return response.text
62
+ except requests.exceptions.RequestException:
63
+ # Both URLs failed
64
+ return ""
65
+
66
+
67
+ def _get_raw_data(reference_date: Timestamp) -> DataFrame:
68
+ url_content = _get_anbima_content(reference_date)
69
+ if url_content == "":
70
+ date_str = reference_date.strftime("%d-%m-%Y")
71
+ raise ValueError(f"Could not fetch ANBIMA data for {date_str}.")
72
+
73
+ df = pd.read_csv(
74
+ io.StringIO(url_content),
75
+ sep="@",
76
+ encoding="latin-1",
77
+ skiprows=2,
78
+ decimal=",",
79
+ thousands=".",
80
+ na_values=["--"],
81
+ dtype_backend="numpy_nullable",
82
+ )
76
83
  return df
77
84
 
78
85
 
79
- def process_raw_data(df_raw: DataFrame) -> DataFrame:
86
+ def _process_raw_data(df_raw: DataFrame) -> DataFrame:
80
87
  """
81
88
  Process raw data from ANBIMA by filtering selected columns, renaming them, and adjusting data formats.
82
89
 
@@ -121,7 +128,6 @@ def process_raw_data(df_raw: DataFrame) -> DataFrame:
121
128
  def get_treasury_rates(
122
129
  reference_date: str | Timestamp | None = None,
123
130
  return_raw=False,
124
- is_anbima_member=False,
125
131
  ) -> DataFrame:
126
132
  """
127
133
  Fetch and process indicative rates from ANBIMA for a specific date.
@@ -130,26 +136,24 @@ def get_treasury_rates(
130
136
 
131
137
  Parameters:
132
138
  - reference_date (str | pd.Timestamp | None): Date for which to fetch the indicative rates.
133
- If None, previous business day based on the Brazilian calendar is used.
139
+ If None, the previous business day based on the Brazilian calendar is used.
134
140
  - return_raw (bool): Whether to return raw data without processing.
135
- - is_anbima_member (bool): Whether the request is being made by an ANBIMA member.
136
141
 
137
142
  Returns:
138
143
  - pd.DataFrame: DataFrame with the indicative rates for the given date.
139
144
  """
140
145
 
141
- normalized_date = normalize_date(reference_date)
142
- df = get_raw_data(normalized_date, is_anbima_member)
146
+ normalized_date = _normalize_date(reference_date)
147
+ df = _get_raw_data(normalized_date)
143
148
 
144
149
  if not return_raw:
145
- df = process_raw_data(df)
150
+ df = _process_raw_data(df)
146
151
 
147
152
  return df
148
153
 
149
154
 
150
155
  def calculate_treasury_di_spreads(
151
156
  reference_date: str | Timestamp | None = None,
152
- is_anbima_member=False,
153
157
  ) -> DataFrame:
154
158
  """
155
159
  Calculate the DI spread for LTN and NTN-F bonds based on ANBIMA's indicative rates.
@@ -159,13 +163,12 @@ def calculate_treasury_di_spreads(
159
163
  Parameters:
160
164
  - reference_date (str | pd.Timestamp | None): The reference date for querying ANBIMA's indicative rates.
161
165
  If None, the previous business day based on the Brazilian calendar is used.
162
- - is_anbima_member (bool): Specifies whether the request is made by an ANBIMA member.
163
166
 
164
167
  Returns:
165
168
  - pd.DataFrame: A DataFrame containing the bond type, reference date, maturity date, and DI spread in basis points.
166
169
  """
167
170
  # Validate the reference date, defaulting to the previous business day if not provided
168
- normalized_date = normalize_date(reference_date)
171
+ normalized_date = _normalize_date(reference_date)
169
172
 
170
173
  # Fetch DI rates and adjust the maturity date format for compatibility
171
174
  df_di = di.get_di(normalized_date)[["ExpirationDate", "SettlementRate"]]
@@ -177,7 +180,7 @@ def calculate_treasury_di_spreads(
177
180
  df_di["MaturityDate"] = df_di["MaturityDate"].dt.to_period("M").dt.to_timestamp()
178
181
 
179
182
  # Fetch bond rates, filtering for LTN and NTN-F types
180
- df_anbima = get_treasury_rates(normalized_date, False, is_anbima_member)
183
+ df_anbima = get_treasury_rates(normalized_date, False)
181
184
  df_anbima.query("BondType in ['LTN', 'NTN-F']", inplace=True)
182
185
 
183
186
  # Merge bond and DI rates by maturity date to calculate spreads
@@ -187,7 +190,7 @@ def calculate_treasury_di_spreads(
187
190
  df_final["DISpread"] = df_final["IndicativeRate"] - df_final["SettlementRate"]
188
191
 
189
192
  # Convert spread to basis points for clarity
190
- df_final["DISpread"] = (BP_CONVERSION_FACTOR * df_final["DISpread"]).round(2)
193
+ df_final["DISpread"] = (BPS_CONVERSION_FACTOR * df_final["DISpread"]).round(2)
191
194
 
192
195
  # Prepare and return the final sorted DataFrame
193
196
  select_columns = ["BondType", "ReferenceDate", "MaturityDate", "DISpread"]
@@ -1,37 +1,52 @@
1
- from typing import Literal, overload
1
+ from typing import Literal, overload, Union
2
+ from datetime import datetime
2
3
 
3
4
  import numpy as np
4
5
  import pandas as pd
5
- from pandas import Series, Timestamp
6
6
 
7
- from .br_holidays import BrHolidays
8
7
 
9
- # Instância global da classe Holidays
8
+ from .holidays import BrHolidays
9
+
10
+ SingleDateType = Union[str, np.datetime64, pd.Timestamp, datetime]
11
+
12
+ SeriesDateType = Union[list, tuple, np.ndarray, pd.Series, pd.Index, pd.DatetimeIndex]
13
+
14
+ TO_TIMESTAMP_TYPES = (str, np.datetime64, datetime)
15
+ TO_SERIES_TYPES = (list, tuple, np.ndarray, pd.Series, pd.Index, pd.DatetimeIndex)
16
+
17
+ # Initialize the BrHolidays class
10
18
  br_holidays = BrHolidays()
11
19
 
12
20
 
13
21
  @overload
14
- def normalize_input_dates(dates: str | Timestamp | None) -> Timestamp: ...
22
+ def _normalize_input_dates(
23
+ dates: SingleDateType | None,
24
+ ) -> pd.Timestamp: ...
15
25
 
16
26
 
17
27
  @overload
18
- def normalize_input_dates(dates: Series) -> Series: ...
28
+ def _normalize_input_dates(
29
+ dates: SeriesDateType,
30
+ ) -> pd.Series: ...
19
31
 
20
32
 
21
- def normalize_input_dates(dates: str | Timestamp | Series | None) -> Timestamp | Series:
22
- if isinstance(dates, str):
23
- return pd.Timestamp(dates).normalize()
24
- elif isinstance(dates, Timestamp):
25
- return dates.normalize()
26
- elif isinstance(dates, Series):
27
- return pd.to_datetime(dates)
28
- elif dates is None:
33
+ def _normalize_input_dates(
34
+ dates: SingleDateType | SeriesDateType | None = None,
35
+ ) -> pd.Timestamp | pd.Series:
36
+ if dates is None:
29
37
  return pd.Timestamp.today().normalize()
38
+ elif isinstance(dates, pd.Timestamp):
39
+ return dates.normalize()
40
+ elif isinstance(dates, TO_TIMESTAMP_TYPES):
41
+ return pd.Timestamp(dates).normalize()
42
+ elif isinstance(dates, TO_SERIES_TYPES):
43
+ result = pd.to_datetime(dates)
44
+ return pd.Series(result).dt.normalize()
30
45
  else:
31
46
  raise ValueError("Invalid date format.")
32
47
 
33
48
 
34
- def is_business_day(date: str | Timestamp | None = None) -> bool:
49
+ def is_business_day(date: SingleDateType | None = None) -> bool:
35
50
  """
36
51
  Checks if the input date is a business day.
37
52
 
@@ -47,13 +62,15 @@ def is_business_day(date: str | Timestamp | None = None) -> bool:
47
62
  >>> yd.is_business_day() # Check if today is a business day
48
63
  True
49
64
  """
50
- normalized_date = normalize_input_dates(date)
65
+ normalized_date = _normalize_input_dates(date)
51
66
  # Shift the date if it is not a business day
52
67
  adjusted_date = offset_bdays(normalized_date, 0)
53
68
  return normalized_date == adjusted_date
54
69
 
55
70
 
56
- def convert_to_numpy_date(dates: Timestamp | Series) -> np.datetime64 | np.ndarray:
71
+ def _convert_to_numpy_date(
72
+ dates: pd.Timestamp | pd.Series,
73
+ ) -> np.datetime64 | np.ndarray:
57
74
  """
58
75
  Converts the input dates to a numpy datetime64[D] format.
59
76
 
@@ -71,28 +88,28 @@ def convert_to_numpy_date(dates: Timestamp | Series) -> np.datetime64 | np.ndarr
71
88
 
72
89
  @overload
73
90
  def offset_bdays(
74
- dates: str | Timestamp | None = None,
91
+ dates: SingleDateType | None = None,
75
92
  offset: int = 0,
76
93
  roll: Literal["forward", "backward"] = "forward",
77
94
  holiday_list: Literal["old", "new", "infer"] = "infer",
78
- ) -> Timestamp: ...
95
+ ) -> pd.Timestamp: ...
79
96
 
80
97
 
81
98
  @overload
82
99
  def offset_bdays(
83
- dates: Series,
100
+ dates: SeriesDateType,
84
101
  offset: int = 0,
85
102
  roll: Literal["forward", "backward"] = "forward",
86
103
  holiday_list: Literal["old", "new", "infer"] = "infer",
87
- ) -> Series: ...
104
+ ) -> pd.Series: ...
88
105
 
89
106
 
90
107
  def offset_bdays(
91
- dates: str | Timestamp | Series | None = None,
108
+ dates: SingleDateType | SeriesDateType | None = None,
92
109
  offset: int = 0,
93
110
  roll: Literal["forward", "backward"] = "forward",
94
111
  holiday_list: Literal["old", "new", "infer"] = "infer",
95
- ) -> Timestamp | Series:
112
+ ) -> pd.Timestamp | pd.Series:
96
113
  """
97
114
  Offsets the dates to the next or previous business day. This function is a wrapper
98
115
  for `numpy.busday_offset` to be used directly with Pandas data types that infers the
@@ -129,14 +146,14 @@ def offset_bdays(
129
146
  >>> yd.offset_bdays(date, -1)
130
147
  Timestamp('2023-12-21') # Offset to the previous business day
131
148
  """
132
- normalized_dates = normalize_input_dates(dates)
149
+ normalized_dates = _normalize_input_dates(dates)
133
150
 
134
151
  selected_holidays = br_holidays.get_applicable_holidays(
135
152
  normalized_dates, holiday_list
136
153
  )
137
- selected_holidays_np = convert_to_numpy_date(selected_holidays)
154
+ selected_holidays_np = _convert_to_numpy_date(selected_holidays)
138
155
 
139
- dates_np = convert_to_numpy_date(normalized_dates)
156
+ dates_np = _convert_to_numpy_date(normalized_dates)
140
157
  offsetted_dates_np = np.busday_offset(
141
158
  dates_np, offsets=offset, roll=roll, holidays=selected_holidays_np
142
159
  )
@@ -149,33 +166,41 @@ def offset_bdays(
149
166
 
150
167
  @overload
151
168
  def count_bdays(
152
- start: str | Timestamp | None = None,
153
- end: str | Timestamp | None = None,
169
+ start: SingleDateType | None = None,
170
+ end: SingleDateType | None = None,
154
171
  holiday_list: Literal["old", "new", "infer"] = "infer",
155
172
  ) -> int: ...
156
173
 
157
174
 
158
175
  @overload
159
176
  def count_bdays(
160
- start: Series,
161
- end: str | Timestamp | Series | None,
177
+ start: SeriesDateType,
178
+ end: SingleDateType | None,
179
+ holiday_list: Literal["old", "new", "infer"] = "infer",
180
+ ) -> pd.Series: ...
181
+
182
+
183
+ @overload
184
+ def count_bdays(
185
+ start: SingleDateType | None,
186
+ end: SeriesDateType,
162
187
  holiday_list: Literal["old", "new", "infer"] = "infer",
163
- ) -> Series: ...
188
+ ) -> pd.Series: ...
164
189
 
165
190
 
166
191
  @overload
167
192
  def count_bdays(
168
- start: str | Timestamp | Series | None,
169
- end: Series,
193
+ start: SeriesDateType,
194
+ end: SeriesDateType,
170
195
  holiday_list: Literal["old", "new", "infer"] = "infer",
171
- ) -> Series: ...
196
+ ) -> pd.Series: ...
172
197
 
173
198
 
174
199
  def count_bdays(
175
- start: str | Timestamp | Series | None = None,
176
- end: str | Timestamp | Series | None = None,
200
+ start: SingleDateType | SeriesDateType | None = None,
201
+ end: SingleDateType | SeriesDateType | None = None,
177
202
  holiday_list: Literal["old", "new", "infer"] = "infer",
178
- ) -> int | Series:
203
+ ) -> int | pd.Series:
179
204
  """
180
205
  Counts the number of business days between a `start` (inclusive) and `end`
181
206
  (exclusive). If an end date is earlier than the start date, the count will be
@@ -192,7 +217,7 @@ def count_bdays(
192
217
  the input.
193
218
 
194
219
  Returns:
195
- int | Series: The number of business days between the start date and end date.
220
+ int | Series | list: The number of business days between the start date and end date.
196
221
  Returns an integer if the result is a single value, otherwise returns a Series.
197
222
 
198
223
  Notes:
@@ -213,34 +238,33 @@ def count_bdays(
213
238
  >>> yd.count_bdays(start, end)
214
239
  pd.Series([22, 40], dtype='int64')
215
240
  """
216
- normalized_start = normalize_input_dates(start)
217
- normalized_end = normalize_input_dates(end)
241
+ normalized_start = _normalize_input_dates(start)
242
+ normalized_end = _normalize_input_dates(end)
218
243
 
219
244
  # Determine which list of holidays to use
220
245
  selected_holidays = br_holidays.get_applicable_holidays(
221
246
  normalized_start, holiday_list
222
247
  )
223
- selected_holidays_np = convert_to_numpy_date(selected_holidays)
248
+ selected_holidays_np = _convert_to_numpy_date(selected_holidays)
224
249
 
225
250
  # Convert inputs to numpy datetime64[D] before calling numpy.busday_count
226
- start_np = convert_to_numpy_date(normalized_start)
227
- end_np = convert_to_numpy_date(normalized_end)
251
+ start_np = _convert_to_numpy_date(normalized_start)
252
+ end_np = _convert_to_numpy_date(normalized_end)
228
253
 
229
254
  result_np = np.busday_count(start_np, end_np, holidays=selected_holidays_np)
230
- if isinstance(result_np, np.ndarray):
231
- # Return pandas Int64 type for type consistency
232
- return pd.Series(result_np, dtype="Int64")
233
- else:
255
+ if isinstance(result_np, np.int64):
234
256
  return int(result_np)
257
+ else:
258
+ return pd.Series(result_np, dtype="Int64")
235
259
 
236
260
 
237
261
  def generate_bdays(
238
- start: str | Timestamp | None = None,
239
- end: str | Timestamp | None = None,
262
+ start: SingleDateType | None = None,
263
+ end: SingleDateType | None = None,
240
264
  inclusive: Literal["both", "neither", "left", "right"] = "both",
241
265
  holiday_list: Literal["old", "new", "infer"] = "infer",
242
266
  **kwargs,
243
- ) -> Series:
267
+ ) -> pd.Series:
244
268
  """
245
269
  Generates a Series of business days between a `start` (inclusive) and `end`
246
270
  (inclusive) that takes into account the list of brazilian holidays as the default.
@@ -283,11 +307,8 @@ def generate_bdays(
283
307
  2023-12-29 2023-12-29
284
308
  dtype: object
285
309
  """
286
- normalized_start = normalize_input_dates(start)
287
- normalized_end = normalize_input_dates(end)
288
-
289
- if isinstance(normalized_start, Series) or isinstance(normalized_end, Series):
290
- raise ValueError("The start and end dates must be single dates.")
310
+ normalized_start = _normalize_input_dates(start)
311
+ normalized_end = _normalize_input_dates(end)
291
312
 
292
313
  selected_holidays = br_holidays.get_applicable_holidays(
293
314
  normalized_start, holiday_list
@@ -0,0 +1,6 @@
1
+ from .core import get_di, read_di
2
+
3
+ __all__ = [
4
+ "get_di",
5
+ "read_di",
6
+ ]
@@ -4,19 +4,20 @@ from pathlib import Path
4
4
  import pandas as pd
5
5
  from pandas import DataFrame, Timestamp
6
6
 
7
- from . import di_web as diw
8
- from . import di_xml as dix
9
- from . import br_calendar as brc
7
+ from . import web as wb
8
+ from . import xml as dix
9
+ from .. import calendar as cl
10
10
 
11
11
 
12
- def normalize_date(trade_date: str | Timestamp | None = None) -> Timestamp:
12
+ def _normalize_date(trade_date: str | Timestamp | None = None) -> Timestamp:
13
13
  if isinstance(trade_date, str):
14
14
  normalized_date = pd.Timestamp(trade_date).normalize()
15
15
  elif isinstance(trade_date, Timestamp):
16
16
  normalized_date = trade_date.normalize()
17
17
  elif trade_date is None:
18
18
  today = pd.Timestamp.today().normalize()
19
- normalized_date = brc.offset_bdays(today, -1)
19
+ # Get last business day before today
20
+ normalized_date = cl.offset_bdays(today, -1)
20
21
  else:
21
22
  raise ValueError("Invalid date format.")
22
23
 
@@ -25,7 +26,7 @@ def normalize_date(trade_date: str | Timestamp | None = None) -> Timestamp:
25
26
  raise ValueError("Trade date cannot be in the future.")
26
27
 
27
28
  # Raise error if the reference date is not a business day
28
- if not brc.is_business_day(normalized_date):
29
+ if not cl.is_business_day(normalized_date):
29
30
  raise ValueError("Trade date must be a business day.")
30
31
 
31
32
  return normalized_date
@@ -88,7 +89,7 @@ def get_expiration_date(expiration_code: str) -> Timestamp:
88
89
  expiration = pd.Timestamp(year, month, 1)
89
90
 
90
91
  # Adjust to the next business day when expiration date is a weekend or a holiday
91
- adj_expiration = brc.offset_bdays(expiration, offset=0)
92
+ adj_expiration = cl.offset_bdays(expiration, offset=0)
92
93
 
93
94
  return adj_expiration
94
95
 
@@ -136,10 +137,10 @@ def get_di(
136
137
 
137
138
  """
138
139
  # Force trade_date to be a pandas Timestamp
139
- normalized_trade_date = normalize_date(trade_date)
140
+ normalized_trade_date = _normalize_date(trade_date)
140
141
 
141
142
  if source_type == "bmf":
142
- return diw.get_di(normalized_trade_date, return_raw)
143
+ return wb.get_di(normalized_trade_date, return_raw)
143
144
  elif source_type in ["b3", "b3s"]:
144
145
  return dix.get_di(normalized_trade_date, source_type, return_raw)
145
146
  else:
@@ -6,8 +6,8 @@ import pandas as pd
6
6
  from pandas import Timestamp, Series, DataFrame
7
7
  import requests
8
8
 
9
- from . import br_calendar as brc
10
- from . import di_futures as dif
9
+ from .. import calendar as cl
10
+ from . import core as cr
11
11
 
12
12
 
13
13
  def get_old_expiration_date(
@@ -66,13 +66,13 @@ def get_old_expiration_date(
66
66
  ExpirationDate = pd.Timestamp(year, month, 1)
67
67
  # Adjust to the next business day when ExpirationDate date is a weekend or a holiday
68
68
  # Must use the old holiday calendar, since this type of contract code was used until 2006
69
- return brc.offset_bdays(ExpirationDate, offset=0, holiday_list="old")
69
+ return cl.offset_bdays(ExpirationDate, offset=0, holiday_list="old")
70
70
 
71
71
  except (KeyError, ValueError):
72
72
  return pd.NaT # type: ignore
73
73
 
74
74
 
75
- def get_raw_di(trade_date: Timestamp) -> DataFrame:
75
+ def _get_raw_di(trade_date: Timestamp) -> DataFrame:
76
76
  """
77
77
  Internal function to fetch raw DI futures data from B3 for a specific trade date.
78
78
 
@@ -120,7 +120,7 @@ def get_raw_di(trade_date: Timestamp) -> DataFrame:
120
120
  return pd.DataFrame()
121
121
 
122
122
 
123
- def convert_prices_to_rates(prices: Series, bd: Series) -> Series:
123
+ def _convert_prices_to_rates(prices: Series, bd: Series) -> Series:
124
124
  """
125
125
  Internal function to convert DI futures prices to rates.
126
126
 
@@ -137,7 +137,7 @@ def convert_prices_to_rates(prices: Series, bd: Series) -> Series:
137
137
  return 100 * rates
138
138
 
139
139
 
140
- def convert_prices_in_older_contracts(df: DataFrame) -> DataFrame:
140
+ def _convert_prices_in_older_contracts(df: DataFrame) -> DataFrame:
141
141
  # Prior to 01/01/2002, prices were not converted to rates
142
142
  convert_cols = [
143
143
  "FirstRate",
@@ -149,7 +149,7 @@ def convert_prices_in_older_contracts(df: DataFrame) -> DataFrame:
149
149
  "LastAskRate",
150
150
  ]
151
151
  for col in convert_cols:
152
- df[col] = convert_prices_to_rates(df[col], df["BDToExpiration"])
152
+ df[col] = _convert_prices_to_rates(df[col], df["BDToExpiration"])
153
153
 
154
154
  # Invert low and high prices
155
155
  df["MinRate"], df["MaxRate"] = df["MaxRate"], df["MinRate"]
@@ -157,7 +157,7 @@ def convert_prices_in_older_contracts(df: DataFrame) -> DataFrame:
157
157
  return df
158
158
 
159
159
 
160
- def process_di(df: DataFrame, trade_date: Timestamp) -> DataFrame:
160
+ def _process_di(df: DataFrame, trade_date: Timestamp) -> DataFrame:
161
161
  """
162
162
  Internal function to process and transform raw DI futures data.
163
163
 
@@ -205,9 +205,9 @@ def process_di(df: DataFrame, trade_date: Timestamp) -> DataFrame:
205
205
  get_old_expiration_date, args=(trade_date,)
206
206
  )
207
207
  else:
208
- df["ExpirationDate"] = df["ExpirationCode"].apply(dif.get_expiration_date)
208
+ df["ExpirationDate"] = df["ExpirationCode"].apply(cr.get_expiration_date)
209
209
 
210
- df["BDToExpiration"] = brc.count_bdays(trade_date, df["ExpirationDate"])
210
+ df["BDToExpiration"] = cl.count_bdays(trade_date, df["ExpirationDate"])
211
211
  # Convert to nullable integer, since other columns use this data type
212
212
  df["BDToExpiration"] = df["BDToExpiration"].astype(pd.Int64Dtype())
213
213
  # Remove expired contracts
@@ -229,9 +229,9 @@ def process_di(df: DataFrame, trade_date: Timestamp) -> DataFrame:
229
229
 
230
230
  # Prior to 17/01/2002 (incluive), prices were not converted to rates
231
231
  if trade_date <= pd.Timestamp("2002-01-17"):
232
- df = convert_prices_in_older_contracts(df)
232
+ df = _convert_prices_in_older_contracts(df)
233
233
 
234
- df["SettlementRate"] = convert_prices_to_rates(
234
+ df["SettlementRate"] = _convert_prices_to_rates(
235
235
  df["SettlementPrice"], df["BDToExpiration"]
236
236
  )
237
237
 
@@ -289,7 +289,7 @@ def get_di(trade_date: Timestamp, return_raw: bool = False) -> DataFrame:
289
289
  - OpenContracts: number of open contracts at the start of the trading day.
290
290
  - closed_contracts: number of closed contracts at the end of the trading day.
291
291
  """
292
- df_raw = get_raw_di(trade_date)
292
+ df_raw = _get_raw_di(trade_date)
293
293
  if return_raw:
294
294
  return df_raw
295
- return process_di(df_raw, trade_date)
295
+ return _process_di(df_raw, trade_date)
@@ -7,11 +7,11 @@ import pandas as pd
7
7
  from pandas import Timestamp, DataFrame
8
8
  from lxml import etree
9
9
 
10
- from . import di_futures as dif
11
- from . import br_calendar as brc
10
+ from . import core as cr
11
+ from .. import calendar as cd
12
12
 
13
13
 
14
- def get_file_from_url(trade_date: Timestamp, source_type: str) -> io.BytesIO:
14
+ def _get_file_from_url(trade_date: Timestamp, source_type: str) -> io.BytesIO:
15
15
  """
16
16
  Types of XML files available:
17
17
  Full Price Report (all assets)
@@ -39,7 +39,7 @@ def get_file_from_url(trade_date: Timestamp, source_type: str) -> io.BytesIO:
39
39
  return io.BytesIO(response.content)
40
40
 
41
41
 
42
- def extract_xml_from_zip(zip_file: io.BytesIO) -> io.BytesIO:
42
+ def _extract_xml_from_zip(zip_file: io.BytesIO) -> io.BytesIO:
43
43
  # First, read the outer file
44
44
  with zipfile.ZipFile(zip_file, "r") as outer_zip:
45
45
  outer_file_name = outer_zip.namelist()[0]
@@ -58,7 +58,7 @@ def extract_xml_from_zip(zip_file: io.BytesIO) -> io.BytesIO:
58
58
  return io.BytesIO(inner_file_content)
59
59
 
60
60
 
61
- def extract_di_data_from_xml(xml_file: io.BytesIO) -> list[dict]:
61
+ def _extract_di_data_from_xml(xml_file: io.BytesIO) -> list[dict]:
62
62
  parser = etree.XMLParser(
63
63
  ns_clean=True, remove_blank_text=True, remove_comments=True, recover=True
64
64
  )
@@ -120,7 +120,7 @@ def extract_di_data_from_xml(xml_file: io.BytesIO) -> list[dict]:
120
120
  return di_data
121
121
 
122
122
 
123
- def create_df_from_di_data(di1_data: list) -> DataFrame:
123
+ def _create_df_from_di_data(di1_data: list) -> DataFrame:
124
124
  # Criar um DataFrame com os dados coletados
125
125
  df = pd.DataFrame(di1_data)
126
126
 
@@ -129,7 +129,7 @@ def create_df_from_di_data(di1_data: list) -> DataFrame:
129
129
  return pd.read_csv(file, dtype_backend="numpy_nullable")
130
130
 
131
131
 
132
- def filter_and_order_pr_df(df: DataFrame) -> DataFrame:
132
+ def _filter_and_order_pr_df(df: DataFrame) -> DataFrame:
133
133
  selected_columns = [
134
134
  "TradDt",
135
135
  "TckrSymb",
@@ -165,7 +165,7 @@ def filter_and_order_pr_df(df: DataFrame) -> DataFrame:
165
165
  return df[selected_columns]
166
166
 
167
167
 
168
- def filter_and_order_sprd_df(df: DataFrame) -> DataFrame:
168
+ def _filter_and_order_sprd_df(df: DataFrame) -> DataFrame:
169
169
  cols = [
170
170
  "TradDt",
171
171
  "TckrSymb",
@@ -187,7 +187,7 @@ def filter_and_order_sprd_df(df: DataFrame) -> DataFrame:
187
187
  return df[cols]
188
188
 
189
189
 
190
- def standardize_column_names(df: DataFrame) -> DataFrame:
190
+ def _standardize_column_names(df: DataFrame) -> DataFrame:
191
191
  rename_dict = {
192
192
  "TradDt": "TradeDate",
193
193
  "TckrSymb": "Ticker",
@@ -224,15 +224,15 @@ def standardize_column_names(df: DataFrame) -> DataFrame:
224
224
  return df.rename(columns=rename_dict)
225
225
 
226
226
 
227
- def process_di_df(df_raw: DataFrame) -> DataFrame:
227
+ def _process_di_df(df_raw: DataFrame) -> DataFrame:
228
228
  df = df_raw.copy()
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(dif.get_expiration_date)
232
+ expiration = df["TckrSymb"].str[3:].apply(cr.get_expiration_date)
233
233
  df.insert(2, "ExpirationDate", expiration)
234
234
 
235
- business_days = brc.count_bdays(df["TradDt"], df["ExpirationDate"])
235
+ business_days = cd.count_bdays(df["TradDt"], df["ExpirationDate"])
236
236
  df.insert(3, "BDToExpiration", business_days)
237
237
 
238
238
  # Convert to nullable integer, since other columns use this data type
@@ -245,29 +245,29 @@ def process_di_df(df_raw: DataFrame) -> DataFrame:
245
245
 
246
246
 
247
247
  def get_di(trade_date: Timestamp, source_type: str, return_raw: bool) -> DataFrame:
248
- zip_file = get_file_from_url(trade_date, source_type)
248
+ zip_file = _get_file_from_url(trade_date, source_type)
249
249
 
250
- xml_file = extract_xml_from_zip(zip_file)
250
+ xml_file = _extract_xml_from_zip(zip_file)
251
251
 
252
- di_data = extract_di_data_from_xml(xml_file)
252
+ di_data = _extract_di_data_from_xml(xml_file)
253
253
 
254
- df_raw = create_df_from_di_data(di_data)
254
+ df_raw = _create_df_from_di_data(di_data)
255
255
  if return_raw:
256
256
  return df_raw
257
257
 
258
258
  # Remove unnecessary columns
259
259
  if source_type == "b3":
260
- df_di = filter_and_order_pr_df(df_raw)
260
+ df_di = _filter_and_order_pr_df(df_raw)
261
261
  elif source_type == "b3s":
262
- df_di = filter_and_order_sprd_df(df_raw)
262
+ df_di = _filter_and_order_sprd_df(df_raw)
263
263
  else:
264
264
  raise ValueError("Invalid source type. Must be 'b3' or 'b3s'.")
265
265
 
266
266
  # Process and transform data
267
- df_di = process_di_df(df_di)
267
+ df_di = _process_di_df(df_di)
268
268
 
269
269
  # Standardize column names
270
- df_di = standardize_column_names(df_di)
270
+ df_di = _standardize_column_names(df_di)
271
271
 
272
272
  return df_di
273
273
 
@@ -280,28 +280,28 @@ def read_di(file_path: Path, return_raw: bool = False) -> DataFrame:
280
280
  else:
281
281
  raise FileNotFoundError(f"No file found at {file_path}.")
282
282
 
283
- xml_file = extract_xml_from_zip(zip_file)
283
+ xml_file = _extract_xml_from_zip(zip_file)
284
284
 
285
- di_data = extract_di_data_from_xml(xml_file)
285
+ di_data = _extract_di_data_from_xml(xml_file)
286
286
 
287
- df_raw = create_df_from_di_data(di_data)
287
+ df_raw = _create_df_from_di_data(di_data)
288
288
  if return_raw:
289
289
  return df_raw
290
290
 
291
291
  # Filename examples: PR231228.zip or SPRD240216.zip
292
292
  file_stem = file_path.stem
293
293
  if "PR" in file_stem:
294
- df_di = filter_and_order_pr_df(df_raw)
294
+ df_di = _filter_and_order_pr_df(df_raw)
295
295
  elif "SPRD" in file_stem:
296
- df_di = filter_and_order_sprd_df(df_raw)
296
+ df_di = _filter_and_order_sprd_df(df_raw)
297
297
  else:
298
298
  raise ValueError("Filename must start with 'PR' or 'SPRD'.")
299
299
 
300
300
  # Process and transform data
301
- df_di = process_di_df(df_di)
301
+ df_di = _process_di_df(df_di)
302
302
 
303
303
  # Standardize column names
304
- df_di = standardize_column_names(df_di)
304
+ df_di = _standardize_column_names(df_di)
305
305
 
306
306
  return df_di
307
307
 
@@ -0,0 +1,3 @@
1
+ from .core import BrHolidays
2
+
3
+ __all__ = ["BrHolidays"]
@@ -31,7 +31,7 @@ dependencies = [
31
31
  "lxml",
32
32
  ]
33
33
  dynamic = []
34
- version = "0.4.0"
34
+ version = "0.5.0"
35
35
 
36
36
  [project.license]
37
37
  file = "LICENSE"
@@ -1,14 +1,14 @@
1
1
  import pandas as pd
2
2
  import numpy as np
3
3
 
4
- from pyield import br_calendar as brc
4
+ from pyield import calendar as cl
5
5
 
6
6
 
7
7
  def test_count_bdays_1():
8
8
  start = "2023-01-01"
9
9
  end = "2023-01-08"
10
10
  # 01/01/2023 is a Sunday and a holiday
11
- assert brc.count_bdays(start, end) == 5
11
+ assert cl.count_bdays(start, end) == 5
12
12
 
13
13
 
14
14
  def test_count_bdays_2():
@@ -16,30 +16,30 @@ def test_count_bdays_2():
16
16
  end = "2024-01-02"
17
17
  # 25/12/2023 is a holiday
18
18
  # 01/01/2024 is a holiday
19
- assert brc.count_bdays(start, end) == 10
19
+ assert cl.count_bdays(start, end) == 10
20
20
 
21
21
 
22
22
  def test_count_bdays_with_series():
23
23
  start = "2023-01-01"
24
24
  end = pd.Series(["2023-01-08", "2023-01-22"])
25
25
  # Assuming no holidays in these periods
26
- assert np.array_equal(brc.count_bdays(start, end), np.array([5, 15]))
26
+ assert np.array_equal(cl.count_bdays(start, end), np.array([5, 15]))
27
27
 
28
28
 
29
29
  def test_count_bdays_negative_count():
30
30
  start = "2023-01-08"
31
31
  end = "2023-01-01"
32
32
  # Negative count expected
33
- assert brc.count_bdays(start, end) == -5
33
+ assert cl.count_bdays(start, end) == -5
34
34
 
35
35
 
36
36
  def test_count_bdays_new_holiday_list():
37
37
  start = "2024-11-20" # Zumbi Nacional Day
38
38
  end = "2024-11-21"
39
- assert brc.count_bdays(start, end) == 0
39
+ assert cl.count_bdays(start, end) == 0
40
40
 
41
41
 
42
42
  def test_count_bdays_old_holiday_list():
43
43
  start = "2020-11-20" # Was not a holiday in 2020
44
44
  end = "2020-11-21"
45
- assert brc.count_bdays(start, end) == 1
45
+ assert cl.count_bdays(start, end) == 1
@@ -1,14 +1,14 @@
1
1
  import pandas as pd
2
2
  import pytest
3
3
 
4
- from pyield import di_futures as dif
5
- from pyield import di_web as diw
4
+ from pyield.di import core as cr
5
+ from pyield.di import web as wb
6
6
 
7
7
 
8
8
  def test_valid_old_contract_code1():
9
9
  expiration_code = "JAN3" # Valid contract code
10
10
  trade_date = pd.Timestamp("2001-05-21")
11
- result = diw.get_old_expiration_date(expiration_code, trade_date)
11
+ result = wb.get_old_expiration_date(expiration_code, trade_date)
12
12
  contract_expiration = pd.Timestamp("2003-01-02")
13
13
  assert result == contract_expiration
14
14
 
@@ -16,7 +16,7 @@ def test_valid_old_contract_code1():
16
16
  def test_valid_old_contract_code2():
17
17
  expiration_code = "JAN3" # Valid contract code
18
18
  trade_date = pd.Timestamp("1990-01-01")
19
- result = diw.get_old_expiration_date(expiration_code, trade_date)
19
+ result = wb.get_old_expiration_date(expiration_code, trade_date)
20
20
  contract_expiration = pd.Timestamp("1993-01-04")
21
21
  assert result == contract_expiration
22
22
 
@@ -25,13 +25,13 @@ def test_invalid_old_contract_code():
25
25
  expiration_code = "J3" # Invalid contract code
26
26
  trade_date = pd.Timestamp("2001-01-02")
27
27
  # Must return NaT
28
- result = diw.get_old_expiration_date(expiration_code, trade_date)
28
+ result = wb.get_old_expiration_date(expiration_code, trade_date)
29
29
  assert pd.isnull(result)
30
30
 
31
31
 
32
32
  def test_new_contract_code():
33
33
  expiration_code = "F23" # Valid contract code
34
- result = dif.get_expiration_date(expiration_code)
34
+ result = cr.get_expiration_date(expiration_code)
35
35
  contract_expiration = pd.Timestamp("2023-01-02")
36
36
  assert result == contract_expiration
37
37
 
@@ -43,7 +43,7 @@ def test_settlement_rate_with_old_holiday_list():
43
43
  }
44
44
 
45
45
  # 22-12-2023 is before the new holiday calendar
46
- df = dif.get_di(trade_date="2023-12-22")
46
+ df = cr.get_di(trade_date="2023-12-22")
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())
@@ -68,7 +68,7 @@ def test_settlement_rates_with_current_holiday_list():
68
68
  "F33": 0.10331,
69
69
  }
70
70
 
71
- df = dif.get_di(trade_date="2023-12-26")
71
+ df = cr.get_di(trade_date="2023-12-26")
72
72
  expiration_codes = list(settlement_rates.keys()) # noqa: F841
73
73
  results = df.query("ExpirationCode in @expiration_codes")[
74
74
  "SettlementRate"
@@ -79,4 +79,4 @@ def test_settlement_rates_with_current_holiday_list():
79
79
  def test_non_business_day():
80
80
  non_business_day = "2023-12-24"
81
81
  with pytest.raises(ValueError):
82
- dif.get_di(trade_date=non_business_day)
82
+ cr.get_di(trade_date=non_business_day)
@@ -1 +0,0 @@
1
- __version__ = "0.4.0"
File without changes
File without changes
File without changes
File without changes