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.
- {pyield-0.3.5 → pyield-0.3.7}/PKG-INFO +6 -16
- {pyield-0.3.5 → pyield-0.3.7}/README.md +5 -15
- pyield-0.3.7/pyield/__about__.py +1 -0
- pyield-0.3.7/pyield/__init__.py +15 -0
- {pyield-0.3.5 → pyield-0.3.7}/pyield/anbima.py +22 -15
- {pyield-0.3.5 → pyield-0.3.7}/pyield/br_calendar.py +132 -48
- {pyield-0.3.5 → pyield-0.3.7}/pyield/br_holidays.py +14 -15
- {pyield-0.3.5 → pyield-0.3.7}/pyield/di_futures.py +10 -5
- {pyield-0.3.5 → pyield-0.3.7}/pyield/di_web.py +10 -8
- {pyield-0.3.5 → pyield-0.3.7}/pyield/di_xml.py +35 -16
- {pyield-0.3.5 → pyield-0.3.7}/pyproject.toml +2 -1
- pyield-0.3.7/tests/__init__.py +0 -0
- pyield-0.3.5/pyield/__about__.py +0 -1
- pyield-0.3.5/pyield/__init__.py +0 -4
- {pyield-0.3.5 → pyield-0.3.7}/LICENSE +0 -0
- {pyield-0.3.5 → pyield-0.3.7}/pyield/br_holidays_new.txt +0 -0
- {pyield-0.3.5 → pyield-0.3.7}/pyield/br_holidays_old.txt +0 -0
- /pyield-0.3.5/tests/__init__.py → /pyield-0.3.7/pyield/py.typed +0 -0
- {pyield-0.3.5 → pyield-0.3.7}/tests/test_br_calendar.py +0 -0
- {pyield-0.3.5 → pyield-0.3.7}/tests/test_di.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PYield
|
|
3
|
-
Version: 0.3.
|
|
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
|
|
55
|
-
- Data Processing
|
|
56
|
-
- Analysis Tools
|
|
57
|
-
- Easy Integration
|
|
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
|
|
13
|
-
- Data Processing
|
|
14
|
-
- Analysis Tools
|
|
15
|
-
- Easy Integration
|
|
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.
|
|
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().
|
|
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 |
|
|
39
|
-
) ->
|
|
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(
|
|
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 =
|
|
108
|
-
df.rename(columns=selected_columns_dict
|
|
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 |
|
|
128
|
+
reference_date: str | Timestamp | None = None,
|
|
122
129
|
return_raw=False,
|
|
123
130
|
is_anbima_member=False,
|
|
124
|
-
) ->
|
|
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 |
|
|
156
|
+
reference_date: str | Timestamp | None = None,
|
|
150
157
|
is_anbima_member=False,
|
|
151
|
-
) ->
|
|
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
|
|
7
|
+
from .br_holidays import BrHolidays
|
|
7
8
|
|
|
8
9
|
# Instância global da classe Holidays
|
|
9
|
-
br_holidays =
|
|
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
|
-
|
|
25
|
-
|
|
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
|
|
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 |
|
|
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
|
-
|
|
104
|
+
formatted_dates = format_input_dates(dates)
|
|
72
105
|
|
|
73
|
-
selected_holidays = br_holidays.get_applicable_holidays(
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
dates_np, offsets=offset, roll="forward", 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
|
-
|
|
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 |
|
|
93
|
-
end (str |
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
184
|
+
pd.Series([22, 40], dtype='int64')
|
|
120
185
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
179
|
-
|
|
254
|
+
formatted_start = format_input_dates(start)
|
|
255
|
+
formatted_end = format_input_dates(end)
|
|
180
256
|
|
|
181
|
-
if
|
|
182
|
-
end
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
5
|
+
from pandas import Series, Timestamp
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class
|
|
8
|
+
class BrHolidays:
|
|
9
9
|
# The date (inclusive) when the new holidays list starts to be valid
|
|
10
|
-
TRANSITION_DATE =
|
|
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) ->
|
|
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
|
-
|
|
22
|
-
|
|
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:
|
|
27
|
+
dates: Timestamp | Series,
|
|
28
28
|
holiday_list: Literal["old", "new", "infer"] = "infer",
|
|
29
|
-
) ->
|
|
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 (
|
|
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
|
-
|
|
40
|
+
pd.Series: The list of holidays to use.
|
|
42
41
|
"""
|
|
43
|
-
if isinstance(dates,
|
|
42
|
+
if isinstance(dates, Timestamp):
|
|
44
43
|
earliest_date = dates
|
|
45
44
|
else:
|
|
46
|
-
earliest_date =
|
|
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 <
|
|
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) ->
|
|
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
|
-
|
|
69
|
+
adj_expiration = brc.offset_bdays(expiration, offset=0)
|
|
70
|
+
|
|
71
|
+
return adj_expiration
|
|
67
72
|
|
|
68
73
|
except (KeyError, ValueError):
|
|
69
|
-
|
|
74
|
+
raise ValueError("Invalid expiration code.")
|
|
70
75
|
|
|
71
76
|
|
|
72
77
|
def get_di(
|
|
73
|
-
trade_date: str |
|
|
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) ->
|
|
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:
|
|
13
|
-
) ->
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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) ->
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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) ->
|
|
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()
|
|
File without changes
|
pyield-0.3.5/pyield/__about__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.5"
|
pyield-0.3.5/pyield/__init__.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|