PYield 0.7.2__tar.gz → 0.7.3__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.7.2 → pyield-0.7.3}/PKG-INFO +8 -8
- {pyield-0.7.2 → pyield-0.7.3}/README.md +7 -7
- pyield-0.7.3/pyield/__about__.py +1 -0
- {pyield-0.7.2 → pyield-0.7.3}/pyield/data_access.py +9 -3
- {pyield-0.7.2 → pyield-0.7.3}/pyield/futures/__init__.py +3 -2
- {pyield-0.7.2 → pyield-0.7.3}/pyield/futures/common.py +56 -3
- {pyield-0.7.2 → pyield-0.7.3}/pyield/futures/ddi.py +5 -5
- {pyield-0.7.2 → pyield-0.7.3}/pyield/futures/di.py +82 -31
- {pyield-0.7.2 → pyield-0.7.3}/pyield/futures/di_xml.py +4 -4
- {pyield-0.7.2 → pyield-0.7.3}/pyield/treasuries.py +1 -1
- {pyield-0.7.2 → pyield-0.7.3}/pyield/utils.py +3 -3
- {pyield-0.7.2 → pyield-0.7.3}/pyproject.toml +1 -1
- {pyield-0.7.2 → pyield-0.7.3}/tests/test_futures.py +24 -26
- pyield-0.7.2/pyield/__about__.py +0 -1
- {pyield-0.7.2 → pyield-0.7.3}/LICENSE +0 -0
- {pyield-0.7.2 → pyield-0.7.3}/pyield/__init__.py +0 -0
- {pyield-0.7.2 → pyield-0.7.3}/pyield/bday.py +0 -0
- {pyield-0.7.2 → pyield-0.7.3}/pyield/data_analysis.py +0 -0
- {pyield-0.7.2 → pyield-0.7.3}/pyield/holidays/__init__.py +0 -0
- {pyield-0.7.2 → pyield-0.7.3}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.7.2 → pyield-0.7.3}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.7.2 → pyield-0.7.3}/pyield/holidays/core.py +0 -0
- {pyield-0.7.2 → pyield-0.7.3}/pyield/indicators.py +0 -0
- {pyield-0.7.2 → pyield-0.7.3}/pyield/py.typed +0 -0
- {pyield-0.7.2 → pyield-0.7.3}/tests/__init__.py +0 -0
- {pyield-0.7.2 → pyield-0.7.3}/tests/test_bday.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PYield
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
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>
|
|
@@ -101,14 +101,14 @@ dtype: datetime64[ns]
|
|
|
101
101
|
# Fetch a DataFrame with the DI Futures data from B3
|
|
102
102
|
>>> yd.fetch_asset(asset_code="DI1", reference_date='2024-03-08')
|
|
103
103
|
|
|
104
|
-
TradeDate
|
|
105
|
-
2024-03-08
|
|
106
|
-
2024-03-08
|
|
107
|
-
2024-03-08
|
|
104
|
+
TradeDate TickerSymbol ExpirationDate BDaysToExp ... LastRate LastAskRate LastBidRate SettlementRate
|
|
105
|
+
2024-03-08 DI1J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
|
|
106
|
+
2024-03-08 DI1K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
|
|
107
|
+
2024-03-08 DI1M24 2024-06-03 58 ... 10.604 10.602 10.604 10.608
|
|
108
108
|
... ... ... ... ... ... ... ... ...
|
|
109
|
-
2024-03-08
|
|
110
|
-
2024-03-08
|
|
111
|
-
2024-03-08
|
|
109
|
+
2024-03-08 DI1F37 2037-01-02 3213 ... <NA> <NA> <NA> 10.859
|
|
110
|
+
2024-03-08 DI1F38 2038-01-04 3462 ... <NA> <NA> <NA> 10.859
|
|
111
|
+
2024-03-08 DI1F39 2039-01-03 3713 ... <NA> <NA> <NA> 10.85
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
### Treasury Bonds Data
|
|
@@ -59,14 +59,14 @@ dtype: datetime64[ns]
|
|
|
59
59
|
# Fetch a DataFrame with the DI Futures data from B3
|
|
60
60
|
>>> yd.fetch_asset(asset_code="DI1", reference_date='2024-03-08')
|
|
61
61
|
|
|
62
|
-
TradeDate
|
|
63
|
-
2024-03-08
|
|
64
|
-
2024-03-08
|
|
65
|
-
2024-03-08
|
|
62
|
+
TradeDate TickerSymbol ExpirationDate BDaysToExp ... LastRate LastAskRate LastBidRate SettlementRate
|
|
63
|
+
2024-03-08 DI1J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
|
|
64
|
+
2024-03-08 DI1K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
|
|
65
|
+
2024-03-08 DI1M24 2024-06-03 58 ... 10.604 10.602 10.604 10.608
|
|
66
66
|
... ... ... ... ... ... ... ... ...
|
|
67
|
-
2024-03-08
|
|
68
|
-
2024-03-08
|
|
69
|
-
2024-03-08
|
|
67
|
+
2024-03-08 DI1F37 2037-01-02 3213 ... <NA> <NA> <NA> 10.859
|
|
68
|
+
2024-03-08 DI1F38 2038-01-04 3462 ... <NA> <NA> <NA> 10.859
|
|
69
|
+
2024-03-08 DI1F39 2039-01-03 3713 ... <NA> <NA> <NA> 10.85
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
### Treasury Bonds Data
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.3"
|
|
@@ -23,7 +23,7 @@ def fetch_asset(
|
|
|
23
23
|
- "DI1": One-day Interbank Deposit Futures (Futuro de DI) from B3.
|
|
24
24
|
- "DDI": DI x U.S. Dollar Spread Futures (Futuro de Cupom Cambial) from B3.
|
|
25
25
|
reference_date (str | pd.Timestamp | None): The reference date for which data is
|
|
26
|
-
fetched. Defaults to the
|
|
26
|
+
fetched. Defaults to the last business day if None.
|
|
27
27
|
**kwargs: Additional keyword arguments, specifically:
|
|
28
28
|
- return_raw (bool): Whether to return raw data without processing. Defaults
|
|
29
29
|
to False.
|
|
@@ -48,7 +48,13 @@ def fetch_asset(
|
|
|
48
48
|
return df.query(f"BondType == '{asset_code.upper()}'")
|
|
49
49
|
|
|
50
50
|
elif asset_code.lower() == "di1":
|
|
51
|
-
|
|
51
|
+
today = pd.Timestamp.today().normalize()
|
|
52
|
+
if normalized_date == today:
|
|
53
|
+
return ft.fetch_last_di_data()
|
|
54
|
+
else:
|
|
55
|
+
return ft.fetch_past_di_data(
|
|
56
|
+
trade_date=normalized_date, return_raw=return_raw
|
|
57
|
+
)
|
|
52
58
|
elif asset_code.lower() == "ddi":
|
|
53
59
|
return ft.fetch_ddi(trade_date=normalized_date, return_raw=return_raw)
|
|
54
60
|
else:
|
|
@@ -67,7 +73,7 @@ def fetch_indicator(
|
|
|
67
73
|
- "SELIC": SELIC target rate from the Central Bank of Brazil.
|
|
68
74
|
- "IPCA": IPCA monthly inflation rate from IBGE.
|
|
69
75
|
reference_date (str | pd.Timestamp | None): The reference date for which data is
|
|
70
|
-
fetched. Defaults to the
|
|
76
|
+
fetched. Defaults to the last business day if None.
|
|
71
77
|
|
|
72
78
|
Returns:
|
|
73
79
|
pd.Series: A Series containing the fetched data for the specified indicator.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from .common import get_expiration_date, get_old_expiration_date
|
|
2
2
|
from .ddi import fetch_ddi
|
|
3
|
-
from .di import
|
|
3
|
+
from .di import fetch_last_di_data, fetch_past_di_data
|
|
4
4
|
|
|
5
5
|
__all__ = [
|
|
6
|
-
"
|
|
6
|
+
"fetch_past_di_data",
|
|
7
|
+
"fetch_last_di_data",
|
|
7
8
|
"fetch_ddi",
|
|
8
9
|
"get_expiration_date",
|
|
9
10
|
"get_old_expiration_date",
|
|
@@ -131,12 +131,12 @@ def get_old_expiration_date(
|
|
|
131
131
|
return pd.NaT # type: ignore
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
def
|
|
134
|
+
def fetch_past_data(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
135
135
|
"""
|
|
136
|
-
|
|
136
|
+
Fetch the historical futures data from B3 for a specific trade date.
|
|
137
137
|
|
|
138
138
|
Args:
|
|
139
|
-
trade_date:
|
|
139
|
+
trade_date (pd.Timestamp): The trade date for which the data should be fetched.
|
|
140
140
|
|
|
141
141
|
Returns:
|
|
142
142
|
pd.DataFrame: Raw DI data as a Pandas pd.DataFrame.
|
|
@@ -173,3 +173,56 @@ def _fetch_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
173
173
|
df["AJUSTE CORRIG. (4)"] = df["AJUSTE CORRIG. (4)"].astype(pd.Float64Dtype())
|
|
174
174
|
|
|
175
175
|
return df
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def fetch_last_data(future_code: str) -> pd.DataFrame:
|
|
179
|
+
"""
|
|
180
|
+
Fetch the latest data for a given future code from B3 derivatives quotation API.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
future_code (str): The future code to fetch data for.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
pd.DataFrame: A DataFrame containing the normalized and cleaned data from the API.
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
Exception: An exception is raised if the data fetch operation fails.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
url = f"https://cotacao.b3.com.br/mds/api/v1/DerivativeQuotation/{future_code}"
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
r = requests.get(url)
|
|
196
|
+
r.raise_for_status() # Check for HTTP request errors
|
|
197
|
+
except requests.exceptions.RequestException:
|
|
198
|
+
raise Exception(f"Failed to fetch data for {future_code}.") from None
|
|
199
|
+
|
|
200
|
+
r.encoding = "utf-8" # Explicitly set response encoding to utf-8 for consistency
|
|
201
|
+
|
|
202
|
+
# Normalize JSON response into a flat table
|
|
203
|
+
df = pd.json_normalize(r.json()["Scty"])
|
|
204
|
+
|
|
205
|
+
# Clean and reformat the DataFrame columns
|
|
206
|
+
df.columns = (df.columns
|
|
207
|
+
.str.replace("SctyQtn.", "")
|
|
208
|
+
.str.replace("asset.AsstSummry.", "")
|
|
209
|
+
) # fmt: skip
|
|
210
|
+
df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
|
|
211
|
+
|
|
212
|
+
# Convert maturity codes to datetime and drop rows with missing values
|
|
213
|
+
df["mtrtyCode"] = pd.to_datetime(df["mtrtyCode"], errors="coerce")
|
|
214
|
+
df.dropna(subset=["mtrtyCode"], inplace=True)
|
|
215
|
+
|
|
216
|
+
# Sort the DataFrame by maturity code and reset the index
|
|
217
|
+
df.sort_values("mtrtyCode", inplace=True, ignore_index=True)
|
|
218
|
+
|
|
219
|
+
# Get current date and time
|
|
220
|
+
now = pd.Timestamp.now().round("s")
|
|
221
|
+
# Subtract 15 minutes from the current time to account for API delay
|
|
222
|
+
trade_ts = now - pd.Timedelta(minutes=15)
|
|
223
|
+
df["TradeTimestamp"] = trade_ts
|
|
224
|
+
|
|
225
|
+
# Convert DataFrame to use nullable data types for better type consistency
|
|
226
|
+
df = df.convert_dtypes(dtype_backend="numpy_nullable")
|
|
227
|
+
|
|
228
|
+
return df
|
|
@@ -59,8 +59,8 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
59
59
|
"VENCTO": "ExpirationCode",
|
|
60
60
|
"CONTR. ABERT.(1)": "OpenContracts", # At the start of the day
|
|
61
61
|
"CONTR. FECH.(2)": "OpenContractsEndSession", # At the end of the day
|
|
62
|
-
"NÚM. NEGOC.": "
|
|
63
|
-
"CONTR. NEGOC.": "
|
|
62
|
+
"NÚM. NEGOC.": "TradeCount",
|
|
63
|
+
"CONTR. NEGOC.": "TradeVolume",
|
|
64
64
|
"VOL.": "FinancialVolume",
|
|
65
65
|
"AJUSTE": "SettlementPrice",
|
|
66
66
|
"AJUSTE ANTER. (3)": "PrevSettlementRate",
|
|
@@ -133,8 +133,8 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
133
133
|
"OpenContracts",
|
|
134
134
|
# "OpenContractsEndSession" since there is no OpenContracts at the end of the
|
|
135
135
|
# day in XML data, it will be removed to avoid confusion with XML data
|
|
136
|
-
"
|
|
137
|
-
"
|
|
136
|
+
"TradeCount",
|
|
137
|
+
"TradeVolume",
|
|
138
138
|
"FinancialVolume",
|
|
139
139
|
"SettlementPrice",
|
|
140
140
|
"MinRate",
|
|
@@ -172,7 +172,7 @@ def fetch_ddi(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFram
|
|
|
172
172
|
- DaysToExpiration: number of business days to ExpirationDate.
|
|
173
173
|
- OpenContracts: number of open contracts at the start of the trading day.
|
|
174
174
|
"""
|
|
175
|
-
df_raw = common.
|
|
175
|
+
df_raw = common.fetch_past_data(asset_code="DDI", trade_date=trade_date)
|
|
176
176
|
if return_raw:
|
|
177
177
|
return df_raw
|
|
178
178
|
return _process_raw_df(df_raw, trade_date)
|
|
@@ -28,12 +28,12 @@ def _convert_prices_in_older_contracts(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
28
28
|
"MinRate",
|
|
29
29
|
"MaxRate",
|
|
30
30
|
"AvgRate",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
31
|
+
"CloseRate",
|
|
32
|
+
"CloseBidRate",
|
|
33
|
+
"CloseAskRate",
|
|
34
34
|
]
|
|
35
35
|
for col in convert_cols:
|
|
36
|
-
df[col] = _convert_prices_to_rates(df[col], df["
|
|
36
|
+
df[col] = _convert_prices_to_rates(df[col], df["BDaysToExp"])
|
|
37
37
|
|
|
38
38
|
# Invert low and high prices
|
|
39
39
|
df["MinRate"], df["MaxRate"] = df["MaxRate"], df["MinRate"]
|
|
@@ -41,7 +41,7 @@ def _convert_prices_in_older_contracts(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
41
41
|
return df
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def
|
|
44
|
+
def _process_past_data(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
45
45
|
"""
|
|
46
46
|
Internal function to process and transform raw DI futures data.
|
|
47
47
|
|
|
@@ -60,8 +60,8 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
60
60
|
"VENCTO": "ExpirationCode",
|
|
61
61
|
"CONTR. ABERT.(1)": "OpenContracts", # At the start of the day
|
|
62
62
|
"CONTR. FECH.(2)": "OpenContractsEndSession", # At the end of the day
|
|
63
|
-
"NÚM. NEGOC.": "
|
|
64
|
-
"CONTR. NEGOC.": "
|
|
63
|
+
"NÚM. NEGOC.": "TradeCount",
|
|
64
|
+
"CONTR. NEGOC.": "TradeVolume",
|
|
65
65
|
"VOL.": "FinancialVolume",
|
|
66
66
|
"AJUSTE": "SettlementPrice",
|
|
67
67
|
"AJUSTE ANTER. (3)": "PrevSettlementRate",
|
|
@@ -70,11 +70,11 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
70
70
|
"PREÇO MÉD.": "AvgRate",
|
|
71
71
|
"PREÇO MÁX.": "MaxRate",
|
|
72
72
|
"PREÇO ABERTU.": "FirstRate",
|
|
73
|
-
"ÚLT. PREÇO": "
|
|
73
|
+
"ÚLT. PREÇO": "CloseRate",
|
|
74
74
|
"VAR. PTOS.": "PointsVariation",
|
|
75
75
|
# Attention: bid/ask rates are inverted
|
|
76
|
-
"ÚLT.OF. COMPRA": "
|
|
77
|
-
"ÚLT.OF. VENDA": "
|
|
76
|
+
"ÚLT.OF. COMPRA": "CloseAskRate",
|
|
77
|
+
"ÚLT.OF. VENDA": "CloseBidRate",
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
df = df.rename(columns=rename_dict)
|
|
@@ -91,11 +91,9 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
91
91
|
else:
|
|
92
92
|
df["ExpirationDate"] = df["ExpirationCode"].apply(common.get_expiration_date)
|
|
93
93
|
|
|
94
|
-
df["
|
|
95
|
-
# Convert to nullable integer, since other columns use this data type
|
|
96
|
-
df["BDaysToExpiration"] = df["BDaysToExpiration"].astype(pd.Int64Dtype())
|
|
94
|
+
df["BDaysToExp"] = bday.count_bdays(trade_date, df["ExpirationDate"])
|
|
97
95
|
# Remove expired contracts
|
|
98
|
-
df.query("
|
|
96
|
+
df.query("BDaysToExp > 0", inplace=True)
|
|
99
97
|
|
|
100
98
|
# Columns where 0 means NaN
|
|
101
99
|
cols_with_nan = [
|
|
@@ -104,9 +102,9 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
104
102
|
"MinRate",
|
|
105
103
|
"MaxRate",
|
|
106
104
|
"AvgRate",
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
105
|
+
"CloseRate",
|
|
106
|
+
"CloseBidRate",
|
|
107
|
+
"CloseAskRate",
|
|
110
108
|
]
|
|
111
109
|
for col in cols_with_nan:
|
|
112
110
|
df[col] = df[col].replace(0, pd.NA)
|
|
@@ -116,7 +114,7 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
116
114
|
df = _convert_prices_in_older_contracts(df)
|
|
117
115
|
|
|
118
116
|
df["SettlementRate"] = _convert_prices_to_rates(
|
|
119
|
-
df["SettlementPrice"], df["
|
|
117
|
+
df["SettlementPrice"], df["BDaysToExp"]
|
|
120
118
|
)
|
|
121
119
|
|
|
122
120
|
# Remove percentage in all rate columns and round to 5 decimal places since it's the
|
|
@@ -125,32 +123,85 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
125
123
|
for col in rate_cols:
|
|
126
124
|
df[col] = (df[col] / 100).round(5)
|
|
127
125
|
|
|
126
|
+
df["TickerSymbol"] = "DI1" + df["ExpirationCode"]
|
|
127
|
+
|
|
128
128
|
# Filter and order columns
|
|
129
129
|
ordered_cols = [
|
|
130
130
|
"TradeDate",
|
|
131
|
-
"
|
|
131
|
+
"TickerSymbol",
|
|
132
|
+
# "ExpirationCode",
|
|
132
133
|
"ExpirationDate",
|
|
133
|
-
"
|
|
134
|
+
"BDaysToExp",
|
|
134
135
|
"OpenContracts",
|
|
135
136
|
# "OpenContractsEndSession" since there is no OpenContracts at the end of the
|
|
136
137
|
# day in XML data, it will be removed to avoid confusion with XML data
|
|
137
|
-
"
|
|
138
|
-
"
|
|
138
|
+
"TradeCount",
|
|
139
|
+
"TradeVolume",
|
|
139
140
|
"FinancialVolume",
|
|
140
141
|
"SettlementPrice",
|
|
142
|
+
"SettlementRate",
|
|
143
|
+
"FirstRate",
|
|
141
144
|
"MinRate",
|
|
142
145
|
"AvgRate",
|
|
143
146
|
"MaxRate",
|
|
144
|
-
"
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"LastBidRate",
|
|
148
|
-
"SettlementRate",
|
|
147
|
+
"CloseAskRate",
|
|
148
|
+
"CloseBidRate",
|
|
149
|
+
"CloseRate",
|
|
149
150
|
]
|
|
150
151
|
return df[ordered_cols]
|
|
151
152
|
|
|
152
153
|
|
|
153
|
-
def
|
|
154
|
+
def _process_last_di_data(raw_df: pd.DataFrame) -> pd.DataFrame:
|
|
155
|
+
df = raw_df.copy()
|
|
156
|
+
|
|
157
|
+
# Columns to be renamed
|
|
158
|
+
rename_columns = {
|
|
159
|
+
"TradeTimestamp": "TradeTimestamp",
|
|
160
|
+
"symb": "TickerSymbol",
|
|
161
|
+
"mtrtyCode": "ExpirationDate",
|
|
162
|
+
"BDaysToExp": "BDaysToExp",
|
|
163
|
+
"opnCtrcts": "OpenContracts",
|
|
164
|
+
"tradQty": "TradeCount",
|
|
165
|
+
"traddCtrctsQty": "TradeVolume",
|
|
166
|
+
"grssAmt": "FinancialVolume",
|
|
167
|
+
"prvsDayAdjstmntPric": "PrevSettlementRate",
|
|
168
|
+
"bottomLmtPric": "MinLimitRate",
|
|
169
|
+
"topLmtPric": "MaxLimitRate",
|
|
170
|
+
"opngPric": "OpenRate",
|
|
171
|
+
"minPric": "MinRate",
|
|
172
|
+
"avrgPric": "AvgRate",
|
|
173
|
+
"maxPric": "MaxRate",
|
|
174
|
+
"buyOffer.price": "LastAskRate",
|
|
175
|
+
"sellOffer.price": "LastBidRate",
|
|
176
|
+
"curPrc": "LastRate",
|
|
177
|
+
}
|
|
178
|
+
# Rename columns
|
|
179
|
+
df = df.rename(columns=rename_columns)
|
|
180
|
+
|
|
181
|
+
df["BDaysToExp"] = bday.count_bdays(df["TradeTimestamp"], df["ExpirationDate"])
|
|
182
|
+
|
|
183
|
+
# Remove percentage in all rate columns
|
|
184
|
+
rate_cols = [col for col in df.columns if "Rate" in col]
|
|
185
|
+
df[rate_cols] = df[rate_cols] / 100
|
|
186
|
+
|
|
187
|
+
# Reorder columns based on the order of the dictionary
|
|
188
|
+
return df[rename_columns.values()]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def fetch_last_di_data() -> pd.DataFrame:
|
|
192
|
+
"""
|
|
193
|
+
Fetch the latest DI futures data from B3.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
pd.DataFrame: A Pandas pd.DataFrame containing the latest DI futures data.
|
|
197
|
+
"""
|
|
198
|
+
raw_df = common.fetch_last_data(future_code="DI1")
|
|
199
|
+
return _process_last_di_data(raw_df)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def fetch_past_di_data(
|
|
203
|
+
trade_date: pd.Timestamp, return_raw: bool = False
|
|
204
|
+
) -> pd.DataFrame:
|
|
154
205
|
"""
|
|
155
206
|
Fetchs the DI futures data for a given date from B3.
|
|
156
207
|
|
|
@@ -170,10 +221,10 @@ def fetch_di(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame
|
|
|
170
221
|
>>> di.fetch_di(pd.Timestamp("2021-01-04"))
|
|
171
222
|
|
|
172
223
|
Notes:
|
|
173
|
-
-
|
|
224
|
+
- BDaysToExp: number of business days to ExpirationDate.
|
|
174
225
|
- OpenContracts: number of open contracts at the start of the trading day.
|
|
175
226
|
"""
|
|
176
|
-
df_raw = common.
|
|
227
|
+
df_raw = common.fetch_past_data(asset_code="DI1", trade_date=trade_date)
|
|
177
228
|
if return_raw:
|
|
178
229
|
return df_raw
|
|
179
|
-
return
|
|
230
|
+
return _process_past_data(df_raw, trade_date)
|
|
@@ -194,7 +194,7 @@ def _standardize_column_names(df: DataFrame) -> DataFrame:
|
|
|
194
194
|
# "MktDataStrmId"
|
|
195
195
|
# "IntlFinVol",
|
|
196
196
|
"OpnIntrst": "OpenContracts",
|
|
197
|
-
"FinInstrmQty": "
|
|
197
|
+
"FinInstrmQty": "TradeVolume",
|
|
198
198
|
"NtlFinVol": "FinancialVolume",
|
|
199
199
|
"AdjstdQt": "SettlementPrice",
|
|
200
200
|
"MinTradLmt": "MinTradeLimitRate",
|
|
@@ -233,13 +233,13 @@ def _process_di_df(df_raw: DataFrame) -> DataFrame:
|
|
|
233
233
|
df.insert(2, "ExpirationDate", expiration)
|
|
234
234
|
|
|
235
235
|
business_days = bday.count_bdays(df["TradDt"], df["ExpirationDate"])
|
|
236
|
-
df.insert(3, "
|
|
236
|
+
df.insert(3, "BDaysToExp", business_days)
|
|
237
237
|
|
|
238
238
|
# Convert to nullable integer, since other columns use this data type
|
|
239
|
-
df["
|
|
239
|
+
df["BDaysToExp"] = df["BDaysToExp"].astype(pd.Int64Dtype())
|
|
240
240
|
|
|
241
241
|
# Remove expired contracts
|
|
242
|
-
df.query("
|
|
242
|
+
df.query("BDaysToExp > 0", inplace=True)
|
|
243
243
|
|
|
244
244
|
return df.sort_values(by=["ExpirationDate"], ignore_index=True)
|
|
245
245
|
|
|
@@ -150,7 +150,7 @@ def calculate_di_spreads(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
150
150
|
bond type and maturity date.
|
|
151
151
|
"""
|
|
152
152
|
# Fetch DI rates and adjust the maturity date format for compatibility
|
|
153
|
-
df_di = ft.
|
|
153
|
+
df_di = ft.fetch_past_di_data(reference_date)[["ExpirationDate", "SettlementRate"]]
|
|
154
154
|
|
|
155
155
|
# Renaming the columns to match the ANBIMA structure
|
|
156
156
|
df_di.rename(columns={"ExpirationDate": "MaturityDate"}, inplace=True)
|
|
@@ -6,7 +6,7 @@ from . import bday
|
|
|
6
6
|
def _normalize_date(reference_date: str | pd.Timestamp | None = None) -> pd.Timestamp:
|
|
7
7
|
"""
|
|
8
8
|
Normalizes the given date to ensure it is a past business day at midnight. If no
|
|
9
|
-
date is provided, it defaults to the
|
|
9
|
+
date is provided, it defaults to the last business day.
|
|
10
10
|
|
|
11
11
|
Args:
|
|
12
12
|
reference_date (str | pd.Timestamp | None): The date to normalize. Can be a
|
|
@@ -38,9 +38,9 @@ def _normalize_date(reference_date: str | pd.Timestamp | None = None) -> pd.Time
|
|
|
38
38
|
# Normalize Timestamp to midnight
|
|
39
39
|
normalized_date = reference_date.normalize()
|
|
40
40
|
elif reference_date is None:
|
|
41
|
-
# If no date is provided, use the
|
|
41
|
+
# If no date is provided, use the last available business day
|
|
42
42
|
today = pd.Timestamp.today().normalize()
|
|
43
|
-
normalized_date = bday.offset_bdays(today,
|
|
43
|
+
normalized_date = bday.offset_bdays(dates=today, offset=0, roll="backward")
|
|
44
44
|
else:
|
|
45
45
|
raise ValueError("Invalid date format.")
|
|
46
46
|
|
|
@@ -37,46 +37,44 @@ def test_new_contract_code():
|
|
|
37
37
|
|
|
38
38
|
def test_settlement_rate_with_old_holiday_list():
|
|
39
39
|
settlement_rates = {
|
|
40
|
-
"
|
|
41
|
-
"
|
|
40
|
+
"DI1N27": 0.09809,
|
|
41
|
+
"DI1F33": 0.10368,
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
# 22-12-2023 is before the new holiday calendar
|
|
45
45
|
test_date = pd.Timestamp("2023-12-22")
|
|
46
|
-
df = ft.
|
|
47
|
-
|
|
48
|
-
result = df.query("
|
|
46
|
+
df = ft.fetch_past_di_data(trade_date=test_date)
|
|
47
|
+
tickers = list(settlement_rates.keys()) # noqa: F841
|
|
48
|
+
result = df.query("TickerSymbol in @tickers")["SettlementRate"].to_list()
|
|
49
49
|
assert result == list(settlement_rates.values())
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def test_settlement_rates_with_current_holiday_list():
|
|
53
53
|
settlement_rates = {
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
54
|
+
"DI1F24": 0.11644,
|
|
55
|
+
"DI1J24": 0.11300,
|
|
56
|
+
"DI1N24": 0.10786,
|
|
57
|
+
"DI1V24": 0.10321,
|
|
58
|
+
"DI1F25": 0.10031,
|
|
59
|
+
"DI1J25": 0.09852,
|
|
60
|
+
"DI1N25": 0.09715,
|
|
61
|
+
"DI1V25": 0.09651,
|
|
62
|
+
"DI1F26": 0.09583,
|
|
63
|
+
"DI1N26": 0.09631,
|
|
64
|
+
"DI1F27": 0.09683,
|
|
65
|
+
"DI1N27": 0.09794,
|
|
66
|
+
"DI1F29": 0.10042,
|
|
67
|
+
"DI1F31": 0.10240,
|
|
68
|
+
"DI1F33": 0.10331,
|
|
69
69
|
}
|
|
70
70
|
test_date = pd.Timestamp("2023-12-26")
|
|
71
|
-
df = ft.
|
|
72
|
-
|
|
73
|
-
results = df.query("
|
|
74
|
-
"SettlementRate"
|
|
75
|
-
].to_list()
|
|
71
|
+
df = ft.fetch_past_di_data(trade_date=test_date)
|
|
72
|
+
tickers = list(settlement_rates.keys()) # noqa: F841
|
|
73
|
+
results = df.query("TickerSymbol in @tickers")["SettlementRate"].to_list()
|
|
76
74
|
assert results == list(settlement_rates.values())
|
|
77
75
|
|
|
78
76
|
|
|
79
77
|
def test_non_business_day():
|
|
80
78
|
non_business_day = pd.Timestamp("2023-12-24")
|
|
81
79
|
with pytest.raises(ValueError):
|
|
82
|
-
ft.
|
|
80
|
+
ft.fetch_past_di_data(trade_date=non_business_day)
|
pyield-0.7.2/pyield/__about__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.7.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|