PYield 0.3.4__tar.gz → 0.3.6__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.4
3
+ Version: 0.3.6
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
 
@@ -65,18 +66,7 @@ pip install pyield
65
66
  ## How to use PYield
66
67
  ### Getting DI Futures Data
67
68
  ```python
68
- import pyield as pyd
69
-
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
69
+ import pyield as yd
80
70
 
81
71
  # Get a pandas dataframe with the DI processed data from B3 (default)
82
72
  >>> yd.get_di(trade_date='2024-03-08')
@@ -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
 
@@ -23,18 +24,7 @@ pip install pyield
23
24
  ## How to use PYield
24
25
  ### Getting DI Futures Data
25
26
  ```python
26
- import pyield as pyd
27
-
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
27
+ import pyield as yd
38
28
 
39
29
  # Get a pandas dataframe with the DI processed data from B3 (default)
40
30
  >>> yd.get_di(trade_date='2024-03-08')
@@ -0,0 +1 @@
1
+ __version__ = "0.3.6"
@@ -1,4 +1,4 @@
1
1
  from .__about__ import __version__ # noqa: F401
2
2
  from .di_futures import get_di, read_di # noqa: F401
3
3
  from .br_calendar import count_bdays, generate_bdays, offset_bdays # noqa: F401
4
- from .anbima import get_anbima_rates, get_anbima_di_spreads # noqa: F401
4
+ from .anbima import get_treasury_rates, calculate_treasury_di_spreads # noqa: F401
@@ -0,0 +1,195 @@
1
+ import pandas as pd
2
+ from urllib.error import HTTPError
3
+
4
+ from . import di_futures as di
5
+ from . import br_calendar as cl
6
+
7
+ # URL Constants
8
+ ANBIMA_NON_MEMBER_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs/"
9
+ ANBIMA_MEMBER_URL = "http://www.anbima.associados.rtm/merc_sec/arqs/"
10
+
11
+ # Constant for conversion to basis points
12
+ BP_CONVERSION_FACTOR = 10_000
13
+
14
+
15
+ def process_reference_date(
16
+ reference_date: str | pd.Timestamp | None = None,
17
+ ) -> pd.Timestamp:
18
+ """
19
+ Process the given reference date, converting it to a pandas Timestamp. If no date is provided,
20
+ use the previous business day based on the Brazilian calendar.
21
+
22
+ Parameters:
23
+ - reference_date (str | pd.Timestamp | None): The reference date to process.
24
+
25
+ Returns:
26
+ - pd.Timestamp: The processed reference date.
27
+ """
28
+ if reference_date: # Force reference_date to be a pd.Timestamp
29
+ processed_date = pd.Timestamp(reference_date)
30
+ else: # If no reference_date is given, use the previous business day
31
+ today = pd.Timestamp.today().normalize()
32
+ processed_date = cl.offset_bdays(today, -1)
33
+
34
+ return processed_date
35
+
36
+
37
+ def get_raw_data(
38
+ reference_date: str | pd.Timestamp | None = None, is_anbima_member: bool = False
39
+ ) -> pd.DataFrame:
40
+ """
41
+ Fetch indicative rates from ANBIMA for a specific date.
42
+
43
+ Parameters:
44
+ - reference_date (pd.Timestamp): Date for which to fetch the indicative rates.
45
+ - is_anbima_member (bool): Whether the request is being made by an ANBIMA member.
46
+
47
+ Returns:
48
+ - pd.DataFrame: DataFrame with the indicative rates for the given date.
49
+ """
50
+ # Process the reference date, defaulting to the previous business day if not provided
51
+ validated_date = process_reference_date(reference_date)
52
+
53
+ # Format the date to match the URL format
54
+ url_date = validated_date.strftime("%y%m%d")
55
+
56
+ # Set the base URL according to the member status
57
+ base_url = ANBIMA_MEMBER_URL if is_anbima_member else ANBIMA_NON_MEMBER_URL
58
+ # url example: https://www.anbima.com.br/informacoes/merc-sec/arqs/ms231128.txt
59
+ url = f"{base_url}ms{url_date}.txt"
60
+
61
+ try:
62
+ df = pd.read_csv(
63
+ url,
64
+ sep="@",
65
+ encoding="latin-1",
66
+ skiprows=2,
67
+ decimal=",",
68
+ thousands=".",
69
+ dtype_backend="numpy_nullable",
70
+ )
71
+ except HTTPError:
72
+ error_date = validated_date.strftime("%d-%m-%Y")
73
+ raise ValueError(f"Failed to get ANBIMA rates for {error_date}")
74
+
75
+ return df
76
+
77
+
78
+ def process_raw_data(df: pd.DataFrame) -> pd.DataFrame:
79
+ """
80
+ Process raw data from ANBIMA by filtering selected columns, renaming them, and adjusting data formats.
81
+
82
+ Parameters:
83
+ - df (pd.DataFrame): Raw data DataFrame to process.
84
+
85
+ Returns:
86
+ - pd.DataFrame: Processed DataFrame.
87
+ """
88
+ # Filter selected columns and rename them
89
+ selected_columns_dict = {
90
+ "Titulo": "BondType",
91
+ "Data Referencia": "ReferenceDate",
92
+ # "Codigo SELIC": "SelicCode",
93
+ # "Data Base/Emissao": "IssueBaseDate",
94
+ "Data Vencimento": "MaturityDate",
95
+ "Tx. Compra": "BidRate",
96
+ "Tx. Venda": "AskRate",
97
+ "Tx. Indicativas": "IndicativeRate",
98
+ "PU": "Price",
99
+ # "Desvio padrao": "StdDev",
100
+ # "Interv. Ind. Inf. (D0)",
101
+ # "Interv. Ind. Sup. (D0)",
102
+ # "Interv. Ind. Inf. (D+1)",
103
+ # "Interv. Ind. Sup. (D+1)",
104
+ # "Criterio": "Criteria",
105
+ }
106
+ select_columns = list(selected_columns_dict.keys())
107
+ df = df[select_columns].copy()
108
+ df.rename(columns=selected_columns_dict, inplace=True)
109
+
110
+ # Remove percentage from rates
111
+ rate_cols = ["BidRate", "AskRate", "IndicativeRate"]
112
+ df[rate_cols] = df[rate_cols] / 100
113
+
114
+ df["ReferenceDate"] = pd.to_datetime(df["ReferenceDate"], format="%Y%m%d")
115
+ df["MaturityDate"] = pd.to_datetime(df["MaturityDate"], format="%Y%m%d")
116
+
117
+ return df.sort_values(["BondType", "MaturityDate"], ignore_index=True)
118
+
119
+
120
+ def get_treasury_rates(
121
+ reference_date: str | pd.Timestamp | None = None,
122
+ return_raw=False,
123
+ is_anbima_member=False,
124
+ ) -> pd.DataFrame:
125
+ """
126
+ Fetch and process indicative rates from ANBIMA for a specific date.
127
+ If no date is provided, the previous business day based on the Brazilian calendar
128
+ is used.
129
+
130
+ Parameters:
131
+ - reference_date (str | pd.Timestamp | None): Date for which to fetch the indicative rates.
132
+ If None, previous business day based on the Brazilian calendar is used.
133
+ - return_raw (bool): Whether to return raw data without processing.
134
+ - is_anbima_member (bool): Whether the request is being made by an ANBIMA member.
135
+
136
+ Returns:
137
+ - pd.DataFrame: DataFrame with the indicative rates for the given date.
138
+ """
139
+
140
+ df = get_raw_data(reference_date, is_anbima_member)
141
+
142
+ if not return_raw:
143
+ df = process_raw_data(df)
144
+
145
+ return df
146
+
147
+
148
+ def calculate_treasury_di_spreads(
149
+ reference_date: str | pd.Timestamp | None = None,
150
+ is_anbima_member=False,
151
+ ) -> pd.DataFrame:
152
+ """
153
+ Calculate the DI spread for LTN and NTN-F bonds based on ANBIMA's indicative rates.
154
+ If no date is provided, the previous business day based on the Brazilian calendar
155
+ is used.
156
+
157
+ Parameters:
158
+ - reference_date (str | pd.Timestamp | None): The reference date for querying ANBIMA's indicative rates.
159
+ If None, the previous business day based on the Brazilian calendar is used.
160
+ - is_anbima_member (bool): Specifies whether the request is made by an ANBIMA member.
161
+
162
+ Returns:
163
+ - pd.DataFrame: A DataFrame containing the bond type, reference date, maturity date, and DI spread in basis points.
164
+ """
165
+ # Process the reference date, defaulting to the previous business day if not provided
166
+ validated_date = process_reference_date(reference_date)
167
+
168
+ # Fetch DI rates and adjust the maturity date format for compatibility
169
+ df_di = di.get_di(validated_date)[["ExpirationDate", "SettlementRate"]]
170
+
171
+ # Renaming the columns to match the ANBIMA structure
172
+ df_di.rename(columns={"ExpirationDate": "MaturityDate"}, inplace=True)
173
+
174
+ # Adjusting maturity date to match bond data format
175
+ df_di["MaturityDate"] = df_di["MaturityDate"].dt.to_period("M").dt.to_timestamp()
176
+
177
+ # Fetch bond rates, filtering for LTN and NTN-F types
178
+ df_anbima = get_treasury_rates(
179
+ reference_date=validated_date, is_anbima_member=is_anbima_member
180
+ )
181
+ df_anbima.query("BondType in ['LTN', 'NTN-F']", inplace=True)
182
+
183
+ # Merge bond and DI rates by maturity date to calculate spreads
184
+ df_final = pd.merge(df_anbima, df_di, how="left", on="MaturityDate")
185
+
186
+ # Calculating the DI spread as the difference between indicative and settlement rates
187
+ df_final["DISpread"] = df_final["IndicativeRate"] - df_final["SettlementRate"]
188
+
189
+ # Convert spread to basis points for clarity
190
+ df_final["DISpread"] = (BP_CONVERSION_FACTOR * df_final["DISpread"]).round(2)
191
+
192
+ # Prepare and return the final sorted DataFrame
193
+ select_columns = ["BondType", "ReferenceDate", "MaturityDate", "DISpread"]
194
+ df_final = df_final[select_columns].copy()
195
+ return df_final.sort_values(["BondType", "MaturityDate"], ignore_index=True)
@@ -3,14 +3,14 @@ from typing import Literal
3
3
  import pandas as pd
4
4
  import numpy as np
5
5
 
6
- from .br_holidays import BRHolidays
6
+ from .br_holidays import BrHolidays
7
7
 
8
8
  # Instância global da classe Holidays
9
- br_holidays = BRHolidays()
9
+ br_holidays = BrHolidays()
10
10
 
11
11
 
12
12
  def convert_to_numpy_date(
13
- dates: str | pd.Timestamp | pd.Series,
13
+ dates: pd.Timestamp | pd.Series,
14
14
  ) -> np.datetime64 | np.ndarray:
15
15
  """
16
16
  Converts the input dates to a numpy datetime64[D] format.
@@ -21,13 +21,10 @@ def convert_to_numpy_date(
21
21
  Returns:
22
22
  np.datetime64 | np.ndarray: The input dates in a numpy datetime64[D] format.
23
23
  """
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")
24
+ if isinstance(dates, pd.Timestamp):
25
+ return np.datetime64(dates, "D")
29
26
  else:
30
- return dates_pd.to_numpy().astype("datetime64[D]")
27
+ return dates.to_numpy().astype("datetime64[D]")
31
28
 
32
29
 
33
30
  def offset_bdays(
@@ -68,17 +65,19 @@ def offset_bdays(
68
65
  >>> yd.offset_bdays(date, -1)
69
66
  Timestamp('2023-12-21') # Offset to the previous business day
70
67
  """
71
- dates_np = convert_to_numpy_date(dates)
68
+ # Assure that the input is in pandas datetime64[ns] format
69
+ dates_pd = pd.to_datetime(dates)
72
70
 
73
- selected_holidays = br_holidays.get_applicable_holidays(dates_np, holiday_list)
71
+ selected_holidays = br_holidays.get_applicable_holidays(dates_pd, holiday_list)
72
+ selected_holidays_np = convert_to_numpy_date(selected_holidays)
74
73
 
75
- # Offset the dates
76
- offsetted_dates = np.busday_offset(
77
- dates_np, offsets=offset, roll="forward", holidays=selected_holidays
74
+ dates_np = convert_to_numpy_date(dates_pd)
75
+ offsetted_dates_np = np.busday_offset(
76
+ dates_np, offsets=offset, roll="forward", holidays=selected_holidays_np
78
77
  )
79
78
 
80
79
  # Convert the dates back to a pandas datetime64[ns] format
81
- return pd.to_datetime(offsetted_dates, unit="ns")
80
+ return pd.to_datetime(offsetted_dates_np, unit="ns")
82
81
 
83
82
 
84
83
  def count_bdays(start, end, holiday_list: Literal["old", "new", "infer"] = "infer"):
@@ -118,19 +117,24 @@ def count_bdays(start, end, holiday_list: Literal["old", "new", "infer"] = "infe
118
117
  >>> yd.count_bdays(start, end)
119
118
  array([22, 40])
120
119
  """
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)
120
+ # Assure that the inputs are in pandas datetime64[ns] format
121
+ start_pd = pd.to_datetime(start)
122
+ end_pd = pd.to_datetime(end)
124
123
 
125
124
  # Determine which list of holidays to use
126
- selected_holidays = br_holidays.get_applicable_holidays(start_np, holiday_list)
125
+ selected_holidays = br_holidays.get_applicable_holidays(start_pd, holiday_list)
126
+ selected_holidays_np = convert_to_numpy_date(selected_holidays)
127
+
128
+ # Convert inputs to numpy datetime64[D] before calling numpy.busday_count
129
+ start_np = convert_to_numpy_date(start_pd)
130
+ end_np = convert_to_numpy_date(end_pd)
127
131
 
128
- return np.busday_count(start_np, end_np, holidays=selected_holidays)
132
+ return np.busday_count(start_np, end_np, holidays=selected_holidays_np)
129
133
 
130
134
 
131
135
  def generate_bdays(
132
- start=None,
133
- end=None,
136
+ start: str | pd.Timestamp | None = None,
137
+ end: str | pd.Timestamp | None = None,
134
138
  inclusive="both",
135
139
  holiday_list: Literal["old", "new", "infer"] = "infer",
136
140
  **kwargs,
@@ -175,15 +179,24 @@ def generate_bdays(
175
179
  2023-12-29 2023-12-29
176
180
  dtype: object
177
181
  """
178
- if start is None:
179
- start = pd.Timestamp.today()
182
+ if start:
183
+ start_pd = pd.to_datetime(start)
184
+ else:
185
+ start_pd = pd.Timestamp.today().normalize()
180
186
 
181
- if end is None:
182
- end = pd.Timestamp.today()
187
+ if end:
188
+ end_pd = pd.to_datetime(end)
189
+ else:
190
+ end_pd = pd.Timestamp.today().normalize()
183
191
 
184
- start_np = convert_to_numpy_date(start)
185
- selected_holidays = br_holidays.get_applicable_holidays(start_np, holiday_list)
192
+ selected_holidays = br_holidays.get_applicable_holidays(start_pd, holiday_list)
193
+ selected_holidays_list = selected_holidays.to_list()
186
194
 
187
195
  return pd.bdate_range(
188
- start, end, freq="C", inclusive=inclusive, holidays=selected_holidays, **kwargs
196
+ start_pd,
197
+ end_pd,
198
+ freq="C",
199
+ inclusive=inclusive,
200
+ holidays=selected_holidays_list,
201
+ **kwargs,
189
202
  )
@@ -2,12 +2,11 @@ from pathlib import Path
2
2
  from typing import Literal
3
3
 
4
4
  import pandas as pd
5
- import numpy as np
6
5
 
7
6
 
8
- class BRHolidays:
7
+ class BrHolidays:
9
8
  # The date (inclusive) when the new holidays list starts to be valid
10
- TRANSITION_DATE = np.datetime64("2023-12-26", "D")
9
+ TRANSITION_DATE = pd.to_datetime("2023-12-26")
11
10
 
12
11
  def __init__(self):
13
12
  current_dir = Path(__file__).parent
@@ -16,34 +15,33 @@ class BRHolidays:
16
15
  self.new_holidays = self._load_holidays(new_holidays_path)
17
16
  self.old_holidays = self._load_holidays(old_holidays_path)
18
17
 
19
- def _load_holidays(self, file_path: Path) -> np.array:
18
+ def _load_holidays(self, file_path: Path) -> pd.Series:
19
+ """Loads the list of holidays from a text file and returns it as a pd.Series of pd.Timestamp."""
20
20
  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]")
21
+ # Convert the dates to a pd.Series of pd.Timestamp
22
+ return pd.to_datetime(df["date"], format="%d/%m/%Y")
24
23
 
25
24
  def get_applicable_holidays(
26
25
  self,
27
- dates: np.datetime64 | np.ndarray,
26
+ dates: pd.Timestamp | pd.Series,
28
27
  holiday_list: Literal["old", "new", "infer"] = "infer",
29
- ) -> np.array:
28
+ ) -> pd.Series:
30
29
  """
31
30
  Returns the correct list of holidays to use based on the most recent date in the input.
32
31
 
33
32
  Args:
34
- dates (np.datetime64 | np.ndarray): The date(s) to use to determine the list of
35
- holidays.
33
+ dates (pd.Timestamp | pd.Series): The date(s) to use to infer the holidays.
36
34
  holiday_list (str): The holidays list to use. Valid options are 'old', 'new' or
37
35
  'infer'. If 'infer' is used, the list of holidays is selected based on the
38
36
  earliest (minimum) date in the input.
39
37
 
40
38
  Returns:
41
- np.array: The list of holidays to use.
39
+ pd.Series: The list of holidays to use.
42
40
  """
43
- if isinstance(dates, np.datetime64):
41
+ if isinstance(dates, pd.Timestamp):
44
42
  earliest_date = dates
45
43
  else:
46
- earliest_date = np.min(dates)
44
+ earliest_date = dates.min()
47
45
 
48
46
  match holiday_list:
49
47
  case "old":
@@ -51,7 +49,7 @@ class BRHolidays:
51
49
  case "new":
52
50
  return self.new_holidays
53
51
  case "infer":
54
- if earliest_date < BRHolidays.TRANSITION_DATE:
52
+ if earliest_date < BrHolidays.TRANSITION_DATE:
55
53
  return self.old_holidays
56
54
  else:
57
55
  return self.new_holidays
@@ -1,13 +1,14 @@
1
- from typing import Literal
1
+ from typing import Literal, Optional
2
2
  from pathlib import Path
3
3
  import pandas as pd
4
+ import pandas.api.types
4
5
 
5
6
  from . import di_web as diw
6
7
  from . import di_xml as dix
7
8
  from . import br_calendar as brc
8
9
 
9
10
 
10
- def get_expiration_date(expiration_code: str) -> pd.Timestamp:
11
+ def get_expiration_date(expiration_code: str) -> Optional[pd.Timestamp]:
11
12
  """
12
13
  Internal function to convert the expiration code into its expiration date.
13
14
 
@@ -66,7 +67,7 @@ def get_expiration_date(expiration_code: str) -> pd.Timestamp:
66
67
  return brc.offset_bdays(expiration, offset=0)
67
68
 
68
69
  except (KeyError, ValueError):
69
- return pd.NaT
70
+ return pd.NaT # type: ignore
70
71
 
71
72
 
72
73
  def get_di(
@@ -1,5 +1,6 @@
1
1
  import io
2
2
  import warnings
3
+ from typing import Optional
3
4
 
4
5
  import pandas as pd
5
6
  import requests
@@ -10,7 +11,7 @@ from . import di_futures as dif
10
11
 
11
12
  def get_old_expiration_date(
12
13
  ExpirationCode: str, trade_date: pd.Timestamp
13
- ) -> pd.Timestamp:
14
+ ) -> Optional[pd.Timestamp]:
14
15
  """
15
16
  Internal function to convert an old DI contract code into its ExpirationDate date. Valid for
16
17
  contract codes up to 21-05-2006.
@@ -67,7 +68,7 @@ def get_old_expiration_date(
67
68
  return brc.offset_bdays(ExpirationDate, offset=0, holiday_list="old")
68
69
 
69
70
  except (KeyError, ValueError):
70
- return pd.NaT
71
+ return pd.NaT # type: ignore
71
72
 
72
73
 
73
74
  def get_raw_di(trade_date: pd.Timestamp) -> pd.DataFrame:
@@ -57,7 +57,7 @@ def extract_xml_from_zip(zip_file: io.BytesIO) -> io.BytesIO:
57
57
  return io.BytesIO(inner_file_content)
58
58
 
59
59
 
60
- def extract_di_data_from_xml(xml_file: io.BytesIO) -> list:
60
+ def extract_di_data_from_xml(xml_file: io.BytesIO) -> list[dict]:
61
61
  parser = etree.XMLParser(
62
62
  ns_clean=True, remove_blank_text=True, remove_comments=True, recover=True
63
63
  )
@@ -69,18 +69,38 @@ def extract_di_data_from_xml(xml_file: io.BytesIO) -> list:
69
69
  '//ns:TckrSymb[starts-with(text(), "DI1")]', namespaces=namespaces
70
70
  )
71
71
 
72
- # Lista para armazenar os dados
72
+ if (
73
+ tckr_symbols is None
74
+ or not isinstance(tckr_symbols, list)
75
+ or len(tckr_symbols) == 0
76
+ ):
77
+ return []
78
+
79
+ # Lista para armazenar os dados com type hinting
80
+ # di_data: list[dict] = []
73
81
  di_data = []
74
82
 
75
83
  # Processar cada TckrSymb encontrado
76
84
  for tckr_symb in tckr_symbols:
77
- # Acessar o elemento PricRpt que contém o TckrSymb
78
- price_report = tckr_symb.getparent().getparent()
85
+ if isinstance(tckr_symb, etree._Element):
86
+ price_report = tckr_symb.getparent()
87
+ if price_report is not None:
88
+ price_report = price_report.getparent()
89
+ else:
90
+ # Handle the case where tckr_symb doesn't have a parent
91
+ continue
92
+ else:
93
+ # Handle the case where tckr_symb is not an _Element
94
+ continue
79
95
 
80
96
  # Extrair a data de negociação
97
+ if price_report is None:
98
+ continue
81
99
  trade_date = price_report.find(".//ns:TradDt/ns:Dt", namespaces)
82
100
 
83
101
  # Preparar o dicionário de dados do ticker com a data de negociação
102
+ if trade_date is None:
103
+ continue
84
104
  ticker_data = {"TradDt": trade_date.text, "TckrSymb": tckr_symb.text}
85
105
 
86
106
  # Acessar o elemento FinInstrmAttrbts que contém o TckrSymb
@@ -31,7 +31,7 @@ dependencies = [
31
31
  "lxml",
32
32
  ]
33
33
  dynamic = []
34
- version = "0.3.4"
34
+ version = "0.3.6"
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.4"
@@ -1,155 +0,0 @@
1
- import pandas as pd
2
- from urllib.error import HTTPError
3
-
4
- from . import di_futures as di
5
-
6
-
7
- def process_reference_date(
8
- reference_date: str | pd.Timestamp | None = None,
9
- ) -> pd.Timestamp:
10
- if reference_date:
11
- # Force reference_date to be a pd.Timestamp
12
- reference_date = pd.Timestamp(reference_date)
13
- else: # If no reference_date is given, use yesterday's date
14
- today = pd.Timestamp.today().date()
15
- yesterday = today - pd.Timedelta(days=1)
16
- reference_date = yesterday
17
-
18
- return reference_date
19
-
20
-
21
- def get_raw_data(reference_date: pd.Timestamp, anbima_member: bool) -> pd.DataFrame:
22
- """
23
- Fetch indicative rates from ANBIMA for a specific date.
24
-
25
- Parameters:
26
- - reference_date: pd.Timestamp for which to fetch the indicative rates.
27
- If None, fetches yesterday's rates.
28
-
29
- Returns:
30
- - A pandas DataFrame with the indicative rates for the given date.
31
- """
32
-
33
- # Example URL: https://www.anbima.com.br/informacoes/merc-sec/arqs/ms231128.txt
34
- url_date = reference_date.strftime("%y%m%d")
35
- if anbima_member:
36
- url = f"http://www.anbima.associados.rtm/merc_sec/arqs/ms{url_date}.txt"
37
- else:
38
- url = f"https://www.anbima.com.br/informacoes/merc-sec/arqs/ms{url_date}.txt"
39
-
40
- try:
41
- df = pd.read_csv(
42
- url,
43
- sep="@",
44
- encoding="latin-1",
45
- skiprows=2,
46
- decimal=",",
47
- thousands=".",
48
- dtype_backend="numpy_nullable",
49
- )
50
- except HTTPError:
51
- error_date = reference_date.strftime("%d-%m-%Y")
52
- raise ValueError(f"Failed to get ANBIMA rates for {error_date}")
53
-
54
- return df
55
-
56
-
57
- def process_raw_data(df: pd.DataFrame) -> pd.DataFrame:
58
- # Filter selected columns and rename them
59
- selected_columns_dict = {
60
- "Titulo": "BondType",
61
- "Data Referencia": "ReferenceDate",
62
- # "Codigo SELIC": "SelicCode",
63
- # "Data Base/Emissao": "IssueBaseDate",
64
- "Data Vencimento": "MaturityDate",
65
- "Tx. Compra": "BidRate",
66
- "Tx. Venda": "AskRate",
67
- "Tx. Indicativas": "IndicativeRate",
68
- "PU": "Price",
69
- # "Desvio padrao": "StdDev",
70
- # "Interv. Ind. Inf. (D0)",
71
- # "Interv. Ind. Sup. (D0)",
72
- # "Interv. Ind. Inf. (D+1)",
73
- # "Interv. Ind. Sup. (D+1)",
74
- # "Criterio": "Criteria",
75
- }
76
- select_columns = list(selected_columns_dict.keys())
77
- df = df[select_columns].copy()
78
- df.rename(columns=selected_columns_dict, inplace=True)
79
-
80
- # Remove percentage from rates
81
- rate_cols = ["BidRate", "AskRate", "IndicativeRate"]
82
- df[rate_cols] = df[rate_cols] / 100
83
-
84
- df["ReferenceDate"] = pd.to_datetime(df["ReferenceDate"], format="%Y%m%d")
85
- df["MaturityDate"] = pd.to_datetime(df["MaturityDate"], format="%Y%m%d")
86
-
87
- return df.sort_values(["BondType", "MaturityDate"], ignore_index=True)
88
-
89
-
90
- def get_anbima_rates(
91
- reference_date: str | pd.Timestamp | None = None,
92
- return_raw=False,
93
- anbima_member=False,
94
- ) -> pd.DataFrame:
95
- """
96
- Fetch indicative rates from ANBIMA for a specific date.
97
-
98
- Parameters:
99
- - reference_date: pd.Timestamp for which to fetch the indicative rates.
100
- If None, fetches yesterday's rates.
101
-
102
- Returns:
103
- - A pandas DataFrame with the indicative rates for the given date.
104
- """
105
- reference_date = process_reference_date(reference_date)
106
- df = get_raw_data(reference_date, anbima_member)
107
-
108
- if return_raw:
109
- return df
110
- else:
111
- return process_raw_data(df)
112
-
113
-
114
- def get_anbima_di_spreads(
115
- reference_date: str | pd.Timestamp | None = None,
116
- anbima_member=False,
117
- ) -> pd.DataFrame:
118
- """
119
- Calcula o prêmio (spread) com relação ao DI nas taxas indicativas da ANBIMA para os títulos LTN e NTN-F.
120
- A coluna DISpread contém o prêmio em bps.
121
-
122
- Parâmetros:
123
- -----------
124
- data_consulta : pd.Timestamp
125
- A data de referência para a consulta das taxas indicativas da ANBIMA.
126
- """
127
- reference_date = process_reference_date(reference_date)
128
-
129
- df_di = di.get_di(reference_date)[["ExpirationDate", "SettlementRate"]]
130
-
131
- df_di.rename(columns={"ExpirationDate": "MaturityDate"}, inplace=True)
132
-
133
- # Ajustar o vencimento para o primeiro dia do mês para concidir com o formato dos títulos
134
- df_di["MaturityDate"] = df_di["MaturityDate"].dt.to_period("M").dt.to_timestamp()
135
-
136
- df_anb = get_anbima_rates(
137
- reference_date=reference_date, anbima_member=anbima_member
138
- )
139
-
140
- # Filtrar apenas os títulos LTN e NTN-F
141
- df_anb.query("BondType in ['LTN', 'NTN-F']", inplace=True)
142
-
143
- # Unir os dois DataFrames
144
- df = pd.merge(df_anb, df_di, how="left", on="MaturityDate")
145
-
146
- # Calcular o prêmio implícito na taxa da ANBIMA
147
- df["DISpread"] = df["IndicativeRate"] - df["SettlementRate"]
148
- df.drop(columns="SettlementRate", inplace=True)
149
-
150
- # Converter o prêmio para bps e arredondar para 2 casas decimais
151
- df["DISpread"] = (10_000 * df["DISpread"]).round(2)
152
-
153
- select_columns = ["BondType", "ReferenceDate", "MaturityDate", "DISpread"]
154
- df = df[select_columns]
155
- return df.sort_values(["BondType", "MaturityDate"], ignore_index=True)
File without changes
File without changes
File without changes