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.
- {pyield-0.4.0 → pyield-0.5.0}/PKG-INFO +1 -1
- pyield-0.5.0/pyield/__about__.py +1 -0
- {pyield-0.4.0 → pyield-0.5.0}/pyield/__init__.py +2 -2
- {pyield-0.4.0 → pyield-0.5.0}/pyield/anbima.py +56 -53
- pyield-0.4.0/pyield/br_calendar.py → pyield-0.5.0/pyield/calendar.py +76 -55
- pyield-0.5.0/pyield/di/__init__.py +6 -0
- pyield-0.4.0/pyield/di_futures.py → pyield-0.5.0/pyield/di/core.py +10 -9
- pyield-0.4.0/pyield/di_web.py → pyield-0.5.0/pyield/di/web.py +14 -14
- pyield-0.4.0/pyield/di_xml.py → pyield-0.5.0/pyield/di/xml.py +27 -27
- pyield-0.5.0/pyield/holidays/__init__.py +3 -0
- {pyield-0.4.0 → pyield-0.5.0}/pyproject.toml +1 -1
- {pyield-0.4.0 → pyield-0.5.0}/tests/test_br_calendar.py +7 -7
- {pyield-0.4.0 → pyield-0.5.0}/tests/test_di.py +9 -9
- pyield-0.4.0/pyield/__about__.py +0 -1
- {pyield-0.4.0 → pyield-0.5.0}/LICENSE +0 -0
- {pyield-0.4.0 → pyield-0.5.0}/README.md +0 -0
- {pyield-0.4.0/pyield → pyield-0.5.0/pyield/holidays}/br_holidays_new.txt +0 -0
- {pyield-0.4.0/pyield → pyield-0.5.0/pyield/holidays}/br_holidays_old.txt +0 -0
- /pyield-0.4.0/pyield/br_holidays.py → /pyield-0.5.0/pyield/holidays/core.py +0 -0
- {pyield-0.4.0 → pyield-0.5.0}/pyield/py.typed +0 -0
- {pyield-0.4.0 → pyield-0.5.0}/tests/__init__.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .__about__ import __version__
|
|
2
|
-
from .
|
|
3
|
-
from .
|
|
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
|
|
6
|
-
from . import
|
|
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
|
-
|
|
16
|
+
BPS_CONVERSION_FACTOR = 10_000
|
|
14
17
|
|
|
15
18
|
|
|
16
|
-
def
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
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 =
|
|
142
|
-
df =
|
|
146
|
+
normalized_date = _normalize_date(reference_date)
|
|
147
|
+
df = _get_raw_data(normalized_date)
|
|
143
148
|
|
|
144
149
|
if not return_raw:
|
|
145
|
-
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 =
|
|
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
|
|
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"] = (
|
|
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
|
-
|
|
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
|
|
22
|
+
def _normalize_input_dates(
|
|
23
|
+
dates: SingleDateType | None,
|
|
24
|
+
) -> pd.Timestamp: ...
|
|
15
25
|
|
|
16
26
|
|
|
17
27
|
@overload
|
|
18
|
-
def
|
|
28
|
+
def _normalize_input_dates(
|
|
29
|
+
dates: SeriesDateType,
|
|
30
|
+
) -> pd.Series: ...
|
|
19
31
|
|
|
20
32
|
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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:
|
|
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 =
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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 =
|
|
154
|
+
selected_holidays_np = _convert_to_numpy_date(selected_holidays)
|
|
138
155
|
|
|
139
|
-
dates_np =
|
|
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:
|
|
153
|
-
end:
|
|
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:
|
|
161
|
-
end:
|
|
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:
|
|
169
|
-
end:
|
|
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:
|
|
176
|
-
end:
|
|
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 =
|
|
217
|
-
normalized_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 =
|
|
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 =
|
|
227
|
-
end_np =
|
|
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.
|
|
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:
|
|
239
|
-
end:
|
|
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 =
|
|
287
|
-
normalized_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
|
|
@@ -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
|
|
8
|
-
from . import
|
|
9
|
-
from
|
|
7
|
+
from . import web as wb
|
|
8
|
+
from . import xml as dix
|
|
9
|
+
from .. import calendar as cl
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
140
|
+
normalized_trade_date = _normalize_date(trade_date)
|
|
140
141
|
|
|
141
142
|
if source_type == "bmf":
|
|
142
|
-
return
|
|
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
|
|
10
|
-
from . import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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] =
|
|
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
|
|
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(
|
|
208
|
+
df["ExpirationDate"] = df["ExpirationCode"].apply(cr.get_expiration_date)
|
|
209
209
|
|
|
210
|
-
df["BDToExpiration"] =
|
|
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 =
|
|
232
|
+
df = _convert_prices_in_older_contracts(df)
|
|
233
233
|
|
|
234
|
-
df["SettlementRate"] =
|
|
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 =
|
|
292
|
+
df_raw = _get_raw_di(trade_date)
|
|
293
293
|
if return_raw:
|
|
294
294
|
return df_raw
|
|
295
|
-
return
|
|
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
|
|
11
|
-
from
|
|
10
|
+
from . import core as cr
|
|
11
|
+
from .. import calendar as cd
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
232
|
+
expiration = df["TckrSymb"].str[3:].apply(cr.get_expiration_date)
|
|
233
233
|
df.insert(2, "ExpirationDate", expiration)
|
|
234
234
|
|
|
235
|
-
business_days =
|
|
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 =
|
|
248
|
+
zip_file = _get_file_from_url(trade_date, source_type)
|
|
249
249
|
|
|
250
|
-
xml_file =
|
|
250
|
+
xml_file = _extract_xml_from_zip(zip_file)
|
|
251
251
|
|
|
252
|
-
di_data =
|
|
252
|
+
di_data = _extract_di_data_from_xml(xml_file)
|
|
253
253
|
|
|
254
|
-
df_raw =
|
|
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 =
|
|
260
|
+
df_di = _filter_and_order_pr_df(df_raw)
|
|
261
261
|
elif source_type == "b3s":
|
|
262
|
-
df_di =
|
|
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 =
|
|
267
|
+
df_di = _process_di_df(df_di)
|
|
268
268
|
|
|
269
269
|
# Standardize column names
|
|
270
|
-
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 =
|
|
283
|
+
xml_file = _extract_xml_from_zip(zip_file)
|
|
284
284
|
|
|
285
|
-
di_data =
|
|
285
|
+
di_data = _extract_di_data_from_xml(xml_file)
|
|
286
286
|
|
|
287
|
-
df_raw =
|
|
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 =
|
|
294
|
+
df_di = _filter_and_order_pr_df(df_raw)
|
|
295
295
|
elif "SPRD" in file_stem:
|
|
296
|
-
df_di =
|
|
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 =
|
|
301
|
+
df_di = _process_di_df(df_di)
|
|
302
302
|
|
|
303
303
|
# Standardize column names
|
|
304
|
-
df_di =
|
|
304
|
+
df_di = _standardize_column_names(df_di)
|
|
305
305
|
|
|
306
306
|
return df_di
|
|
307
307
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
import numpy as np
|
|
3
3
|
|
|
4
|
-
from pyield import
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5
|
-
from pyield import
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
82
|
+
cr.get_di(trade_date=non_business_day)
|
pyield-0.4.0/pyield/__about__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|