PYield 0.7.7__tar.gz → 0.7.8__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.
Files changed (34) hide show
  1. {pyield-0.7.7 → pyield-0.7.8}/PKG-INFO +7 -7
  2. {pyield-0.7.7 → pyield-0.7.8}/README.md +5 -5
  3. pyield-0.7.8/pyield/__about__.py +1 -0
  4. {pyield-0.7.7 → pyield-0.7.8}/pyield/data_access.py +22 -14
  5. pyield-0.7.8/pyield/futures/historical/__init__.py +6 -0
  6. {pyield-0.7.7/pyield/futures → pyield-0.7.8/pyield/futures/historical}/common.py +55 -86
  7. pyield-0.7.8/pyield/futures/historical/dap.py +62 -0
  8. {pyield-0.7.7/pyield/futures → pyield-0.7.8/pyield/futures/historical}/ddi.py +7 -28
  9. {pyield-0.7.7/pyield/futures → pyield-0.7.8/pyield/futures/historical}/di.py +7 -76
  10. {pyield-0.7.7/pyield/futures → pyield-0.7.8/pyield/futures/historical}/di_xml.py +2 -2
  11. {pyield-0.7.7/pyield/futures → pyield-0.7.8/pyield/futures/historical}/frc.py +7 -8
  12. pyield-0.7.8/pyield/futures/intraday/__init__.py +3 -0
  13. pyield-0.7.8/pyield/futures/intraday/trading_data.py +107 -0
  14. {pyield-0.7.7 → pyield-0.7.8}/pyield/treasuries.py +2 -2
  15. {pyield-0.7.7 → pyield-0.7.8}/pyproject.toml +2 -2
  16. pyield-0.7.8/tests/__init__.py +0 -0
  17. pyield-0.7.8/tests/test_futures.py +38 -0
  18. pyield-0.7.7/pyield/__about__.py +0 -1
  19. pyield-0.7.7/pyield/futures/__init__.py +0 -13
  20. pyield-0.7.7/tests/test_futures.py +0 -80
  21. {pyield-0.7.7 → pyield-0.7.8}/LICENSE +0 -0
  22. {pyield-0.7.7 → pyield-0.7.8}/pyield/__init__.py +0 -0
  23. {pyield-0.7.7 → pyield-0.7.8}/pyield/bday.py +0 -0
  24. {pyield-0.7.7 → pyield-0.7.8}/pyield/data_analysis.py +0 -0
  25. {pyield-0.7.7/tests → pyield-0.7.8/pyield/futures}/__init__.py +0 -0
  26. {pyield-0.7.7 → pyield-0.7.8}/pyield/holidays/__init__.py +0 -0
  27. {pyield-0.7.7 → pyield-0.7.8}/pyield/holidays/br_holidays_new.txt +0 -0
  28. {pyield-0.7.7 → pyield-0.7.8}/pyield/holidays/br_holidays_old.txt +0 -0
  29. {pyield-0.7.7 → pyield-0.7.8}/pyield/holidays/core.py +0 -0
  30. {pyield-0.7.7 → pyield-0.7.8}/pyield/indicators.py +0 -0
  31. {pyield-0.7.7 → pyield-0.7.8}/pyield/projections.py +0 -0
  32. {pyield-0.7.7 → pyield-0.7.8}/pyield/py.typed +0 -0
  33. {pyield-0.7.7 → pyield-0.7.8}/pyield/utils.py +0 -0
  34. {pyield-0.7.7 → pyield-0.7.8}/tests/test_bday.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PYield
3
- Version: 0.7.7
3
+ Version: 0.7.8
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>
@@ -32,13 +32,13 @@ Classifier: Intended Audience :: Financial and Insurance Industry
32
32
  Classifier: Topic :: Office/Business :: Financial :: Investment
33
33
  Project-URL: Source, https://github.com/crdcj/PYield
34
34
  Requires-Python: >=3.11
35
- Requires-Dist: requests
36
35
  Requires-Dist: pandas>=2.0.0
37
36
  Requires-Dist: numpy
38
37
  Requires-Dist: beautifulsoup4
39
38
  Requires-Dist: html5lib
40
39
  Requires-Dist: lxml
41
40
  Requires-Dist: python-calamine>=0.2.0
41
+ Requires-Dist: requests>=2.31.0
42
42
  Description-Content-Type: text/markdown
43
43
 
44
44
  [![PyPI version](https://img.shields.io/pypi/v/pyield.svg)](https://pypi.python.org/pypi/pyield)
@@ -101,7 +101,7 @@ dtype: datetime64[ns]
101
101
  ```python
102
102
  # Fetch current DI Futures data from B3 (15 minutes delay)
103
103
  >>> yd.fetch_asset(asset_code="DI1")
104
- TradeTimestamp TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate LastBidRate LastRate
104
+ TradeTimestamp TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate LastBidRate CurrentRate
105
105
  2024-04-21 13:37:39 DI1K24 2024-05-02 7 ... 0.10660 0.10652 0.10660 0.10660
106
106
  2024-04-21 13:37:39 DI1M24 2024-06-03 28 ... 0.10518 0.10510 0.10516 0.10518
107
107
  2024-04-21 13:37:39 DI1N24 2024-07-01 48 ... 0.10480 0.10456 0.10462 0.10460
@@ -158,11 +158,11 @@ BondType ReferenceDate MaturityDate DISpread
158
158
  ```python
159
159
  # Fetch the SELIC target rate from the Central Bank of Brazil
160
160
  >>> yd.fetch_indicator(indicator_code="SELIC", reference_date='2024-04-12')
161
- 10.75
161
+ 0.1075 # 10.75%
162
162
 
163
163
  # Fetch the IPCA monthly inflation rate from IBGE
164
164
  >>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-03-18')
165
- 0.16
165
+ 0.16 # 0.16%
166
166
 
167
167
  # If no data is yet available for the indicator, the function returns None
168
168
  >>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-04-10')
@@ -178,9 +178,9 @@ IndicatorProjection(
178
178
  last_updated=Timestamp('2024-04-19 18:55:00'),
179
179
  reference_month_ts=Timestamp('2024-04-01 00:00:00'),
180
180
  reference_month_br='ABR/2024',
181
- projected_value=0.0035
181
+ projected_value=0.0035 # 0.35%
182
182
  )
183
183
  >>> ipca.projected_value
184
- 0.0035
184
+ 0.0035 # 0.35%
185
185
  ```
186
186
 
@@ -58,7 +58,7 @@ dtype: datetime64[ns]
58
58
  ```python
59
59
  # Fetch current DI Futures data from B3 (15 minutes delay)
60
60
  >>> yd.fetch_asset(asset_code="DI1")
61
- TradeTimestamp TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate LastBidRate LastRate
61
+ TradeTimestamp TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate LastBidRate CurrentRate
62
62
  2024-04-21 13:37:39 DI1K24 2024-05-02 7 ... 0.10660 0.10652 0.10660 0.10660
63
63
  2024-04-21 13:37:39 DI1M24 2024-06-03 28 ... 0.10518 0.10510 0.10516 0.10518
64
64
  2024-04-21 13:37:39 DI1N24 2024-07-01 48 ... 0.10480 0.10456 0.10462 0.10460
@@ -115,11 +115,11 @@ BondType ReferenceDate MaturityDate DISpread
115
115
  ```python
116
116
  # Fetch the SELIC target rate from the Central Bank of Brazil
117
117
  >>> yd.fetch_indicator(indicator_code="SELIC", reference_date='2024-04-12')
118
- 10.75
118
+ 0.1075 # 10.75%
119
119
 
120
120
  # Fetch the IPCA monthly inflation rate from IBGE
121
121
  >>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-03-18')
122
- 0.16
122
+ 0.16 # 0.16%
123
123
 
124
124
  # If no data is yet available for the indicator, the function returns None
125
125
  >>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-04-10')
@@ -135,9 +135,9 @@ IndicatorProjection(
135
135
  last_updated=Timestamp('2024-04-19 18:55:00'),
136
136
  reference_month_ts=Timestamp('2024-04-01 00:00:00'),
137
137
  reference_month_br='ABR/2024',
138
- projected_value=0.0035
138
+ projected_value=0.0035 # 0.35%
139
139
  )
140
140
  >>> ipca.projected_value
141
- 0.0035
141
+ 0.0035 # 0.35%
142
142
  ```
143
143
 
@@ -0,0 +1 @@
1
+ __version__ = "0.7.8"
@@ -1,9 +1,10 @@
1
1
  import pandas as pd
2
2
 
3
- from . import futures as ft
4
3
  from . import indicators as it
5
4
  from . import projections as pr
6
5
  from . import treasuries as tr
6
+ from .futures import historical as fh
7
+ from .futures import intraday as fi
7
8
  from .utils import _normalize_date
8
9
 
9
10
 
@@ -24,6 +25,7 @@ def fetch_asset(
24
25
  - "DI1": One-day Interbank Deposit Futures (Futuro de DI) from B3.
25
26
  - "DDI": DI x U.S. Dollar Spread Futures (Futuro de Cupom Cambial) from B3.
26
27
  - "FRC": Forward Rate Agreement (FRA) from B3.
28
+ - "DAP": DI x IPCA Spread Futures
27
29
  reference_date (str | pd.Timestamp | None): The reference date for which data is
28
30
  fetched. Defaults to the last business day if None.
29
31
  **kwargs: Additional keyword arguments, specifically:
@@ -43,24 +45,30 @@ def fetch_asset(
43
45
  return_raw = kwargs.get("return_raw", False)
44
46
  normalized_date = _normalize_date(reference_date)
45
47
 
48
+ today = pd.Timestamp.today().normalize()
49
+ if normalized_date == today:
50
+ return fi.fetch_intraday(future_code=asset_code.upper())
51
+
46
52
  if asset_code.lower() == "trb":
47
53
  return tr.fetch_bonds(reference_date=normalized_date, return_raw=return_raw)
48
- elif asset_code.lower() in ["ltn", "lft", "ntn-f", "ntn-b"]:
54
+
55
+ if asset_code.lower() in ["ltn", "lft", "ntn-f", "ntn-b"]:
49
56
  df = tr.fetch_bonds(reference_date=normalized_date)
50
57
  return df.query(f"BondType == '{asset_code.upper()}'")
51
58
 
52
- elif asset_code.lower() == "di1":
53
- today = pd.Timestamp.today().normalize()
54
- if normalized_date == today:
55
- return ft.fetch_last_di()
56
- else:
57
- return ft.fetch_past_di(trade_date=normalized_date, return_raw=return_raw)
58
- elif asset_code.lower() == "ddi":
59
- return ft.fetch_past_ddi(trade_date=normalized_date, return_raw=return_raw)
60
- elif asset_code.lower() == "frc":
61
- return ft.fetch_past_frc(trade_date=normalized_date, return_raw=return_raw)
62
- else:
63
- raise ValueError("Asset type not supported.")
59
+ if asset_code.lower() == "di1":
60
+ return fh.fetch_di(trade_date=normalized_date, return_raw=return_raw)
61
+
62
+ if asset_code.lower() == "ddi":
63
+ return fh.fetch_ddi(trade_date=normalized_date, return_raw=return_raw)
64
+
65
+ if asset_code.lower() == "frc":
66
+ return fh.fetch_frc(trade_date=normalized_date, return_raw=return_raw)
67
+
68
+ if asset_code.lower() == "dap":
69
+ return fh.fetch_dap(trade_date=normalized_date, return_raw=return_raw)
70
+
71
+ raise ValueError("Asset type not supported.")
64
72
 
65
73
 
66
74
  def fetch_indicator(
@@ -0,0 +1,6 @@
1
+ from .dap import fetch_dap
2
+ from .ddi import fetch_ddi
3
+ from .di import fetch_di
4
+ from .frc import fetch_frc
5
+
6
+ __all__ = ["fetch_di", "fetch_ddi", "fetch_frc", "fetch_dap"]
@@ -3,29 +3,7 @@ import io
3
3
  import pandas as pd
4
4
  import requests
5
5
 
6
- from .. import bday
7
-
8
- COLUMNS_MAPPING = {
9
- "VENCTO": "ExpirationCode",
10
- "CONTR. ABERT.(1)": "OpenContracts", # At the start of the day
11
- "CONTR. FECH.(2)": "OpenContractsEndSession", # At the end of the day
12
- "NÚM. NEGOC.": "TradeCount",
13
- "CONTR. NEGOC.": "TradeVolume",
14
- "VOL.": "FinancialVolume",
15
- "AJUSTE": "SettlementPrice",
16
- "AJUSTE ANTER. (3)": "PrevSettlementRate",
17
- "AJUSTE CORRIG. (4)": "AdjSettlementRate",
18
- "AJUSTE DE REF.": "SettlementRate", # FRC
19
- "PREÇO MÍN.": "MinRate",
20
- "PREÇO MÉD.": "AvgRate",
21
- "PREÇO MÁX.": "MaxRate",
22
- "PREÇO ABERTU.": "FirstRate",
23
- "ÚLT. PREÇO": "CloseRate",
24
- "VAR. PTOS.": "PointsVariation",
25
- # Attention: bid/ask rates are inverted
26
- "ÚLT.OF. COMPRA": "CloseAskRate",
27
- "ÚLT.OF. VENDA": "CloseBidRate",
28
- }
6
+ from ... import bday
29
7
 
30
8
 
31
9
  def get_expiration_date(expiration_code: str) -> pd.Timestamp:
@@ -153,7 +131,28 @@ def get_old_expiration_date(
153
131
  return pd.NaT # type: ignore
154
132
 
155
133
 
156
- def fetch_past_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
134
+ def convert_prices_to_rates(
135
+ prices: pd.Series, days_to_expiration: pd.Series, count_convention: int
136
+ ) -> pd.Series:
137
+ """
138
+ Internal function to convert DI futures prices to rates.
139
+
140
+ Args:
141
+ prices (pd.Series): The futures prices to be converted.
142
+ days_to_expiration (pd.Series): The number of days to expiration for each price.
143
+ count_convention (int): The count convention for the DI futures contract.
144
+ Normally, it is 252 business days or 360 calendar days.
145
+
146
+ Returns:
147
+ pd.Series: A pd.Series containing the futures rates.
148
+ """
149
+ rates = (100_000 / prices) ** (count_convention / days_to_expiration) - 1
150
+
151
+ # Round to 5 (3 in %) dec. places (contract's current max. precision)
152
+ return rates.round(5)
153
+
154
+
155
+ def fetch_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
157
156
  """
158
157
  Fetch the historical futures data from B3 for a specific trade date. If the data is
159
158
  not available, an empty DataFrame is returned.
@@ -199,60 +198,7 @@ def fetch_past_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame
199
198
  return df
200
199
 
201
200
 
202
- def fetch_last_raw_df(future_code: str) -> pd.DataFrame:
203
- """
204
- Fetch the latest data for a given future code from B3 derivatives quotation API.
205
-
206
- Args:
207
- future_code (str): The future code to fetch data for.
208
-
209
- Returns:
210
- pd.DataFrame: A DataFrame containing the normalized and cleaned data from the API.
211
-
212
- Raises:
213
- Exception: An exception is raised if the data fetch operation fails.
214
- """
215
-
216
- url = f"https://cotacao.b3.com.br/mds/api/v1/DerivativeQuotation/{future_code}"
217
-
218
- try:
219
- r = requests.get(url)
220
- r.raise_for_status() # Check for HTTP request errors
221
- except requests.exceptions.RequestException:
222
- raise Exception(f"Failed to fetch data for {future_code}.") from None
223
-
224
- r.encoding = "utf-8" # Explicitly set response encoding to utf-8 for consistency
225
-
226
- # Normalize JSON response into a flat table
227
- df = pd.json_normalize(r.json()["Scty"])
228
-
229
- # Clean and reformat the DataFrame columns
230
- df.columns = (df.columns
231
- .str.replace("SctyQtn.", "")
232
- .str.replace("asset.AsstSummry.", "")
233
- ) # fmt: skip
234
- df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
235
-
236
- # Convert maturity codes to datetime and drop rows with missing values
237
- df["mtrtyCode"] = pd.to_datetime(df["mtrtyCode"], errors="coerce")
238
- df.dropna(subset=["mtrtyCode"], inplace=True)
239
-
240
- # Sort the DataFrame by maturity code and reset the index
241
- df.sort_values("mtrtyCode", inplace=True, ignore_index=True)
242
-
243
- # Get current date and time
244
- now = pd.Timestamp.now().round("s")
245
- # Subtract 15 minutes from the current time to account for API delay
246
- trade_ts = now - pd.Timedelta(minutes=15)
247
- df["TradeTimestamp"] = trade_ts
248
-
249
- # Convert DataFrame to use nullable data types for better type consistency
250
- df = df.convert_dtypes(dtype_backend="numpy_nullable")
251
-
252
- return df
253
-
254
-
255
- def rename_columns(df: pd.DataFrame):
201
+ def rename_columns(df: pd.DataFrame) -> pd.DataFrame:
256
202
  all_columns = {
257
203
  "VENCTO": "ExpirationCode",
258
204
  "CONTR. ABERT.(1)": "OpenContracts", # At the start of the day
@@ -274,13 +220,39 @@ def rename_columns(df: pd.DataFrame):
274
220
  "ÚLT.OF. COMPRA": "CloseAskRate",
275
221
  "ÚLT.OF. VENDA": "CloseBidRate",
276
222
  }
277
- rename_dict = {}
278
- for col in all_columns:
279
- if col in df.columns:
280
- rename_dict[col] = all_columns[col]
223
+ rename_dict = {c: all_columns[c] for c in all_columns if c in df.columns}
281
224
  return df.rename(columns=rename_dict)
282
225
 
283
226
 
227
+ def process_raw_df(
228
+ df: pd.DataFrame, trade_date: pd.Timestamp, asset_code: str
229
+ ) -> pd.DataFrame:
230
+ df = rename_columns(df)
231
+
232
+ df["TradeDate"] = trade_date
233
+ # Convert to datetime64[ns] since it is pandas default type for timestamps
234
+ df["TradeDate"] = df["TradeDate"].astype("datetime64[ns]")
235
+
236
+ df["TickerSymbol"] = asset_code + df["ExpirationCode"]
237
+
238
+ # Contract code format was changed in 22/05/2006
239
+ if trade_date < pd.Timestamp("2006-05-22"):
240
+ df["ExpirationDate"] = df["ExpirationCode"].apply(
241
+ get_old_expiration_date, args=(trade_date,)
242
+ )
243
+ else:
244
+ df["ExpirationDate"] = df["ExpirationCode"].apply(get_expiration_date)
245
+
246
+ # Columns where 0 means NaN
247
+ cols_with_nan = [col for col in df.columns if "Rate" in col]
248
+ if "SettlementPrice" in df.columns:
249
+ cols_with_nan.append("SettlementPrice")
250
+ # Replace 0 with NaN in these columns
251
+ df[cols_with_nan] = df[cols_with_nan].replace(0, pd.NA)
252
+
253
+ return df
254
+
255
+
284
256
  def reorder_columns(df: pd.DataFrame):
285
257
  all_columns = [
286
258
  "TradeDate",
@@ -305,8 +277,5 @@ def reorder_columns(df: pd.DataFrame):
305
277
  "CloseBidRate",
306
278
  "CloseRate",
307
279
  ]
308
- reordered_columns = []
309
- for col in all_columns:
310
- if col in df.columns:
311
- reordered_columns.append(col)
280
+ reordered_columns = [col for col in all_columns if col in df.columns]
312
281
  return df[reordered_columns]
@@ -0,0 +1,62 @@
1
+ import pandas as pd
2
+
3
+ from ... import bday as bd
4
+ from . import common as cm
5
+
6
+
7
+ def _process_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
8
+ """
9
+ Internal function to process and transform raw DI futures data.
10
+
11
+ Args:
12
+ df (pd.DataFrame): the raw DI DataFrame.
13
+ trade_date: a datetime-like object representing the trade date.
14
+
15
+ Returns:
16
+ pd.DataFrame: Processed and transformed data as a Pandas pd.DataFrame.
17
+ """
18
+ df["BDaysToExp"] = bd.count_bdays(trade_date, df["ExpirationDate"])
19
+
20
+ # Remove expired contracts
21
+ df.query("BDaysToExp > 0", inplace=True)
22
+
23
+ rate_cols = [col for col in df.columns if "Rate" in col]
24
+ # Remove % and round to 5 (3 in %) dec. places in rate columns
25
+ df[rate_cols] = df[rate_cols].div(100).round(5)
26
+
27
+ # Calculate SettlementRate
28
+ df["SettlementRate"] = cm.convert_prices_to_rates(
29
+ prices=df["SettlementPrice"],
30
+ days_to_expiration=df["BDaysToExp"],
31
+ count_convention=252,
32
+ )
33
+
34
+ return df
35
+
36
+
37
+ def fetch_dap(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
38
+ """
39
+ Fetchs the DI futures data for a given date from B3.
40
+
41
+ This function fetches and processes the DI futures data from B3 for a specific
42
+ trade date. It's the primary external interface for accessing DI data. If the data
43
+ is not available, an empty DataFrame is returned.
44
+
45
+ Args:
46
+ trade_date (pd.Timestamp): The trade date to fetch the DI futures data.
47
+ return_raw (bool): If True, returns the raw data as a Pandas pd.DataFrame.
48
+ Defaults to False.
49
+
50
+ Returns:
51
+ pd.DataFrame: A Pandas pd.DataFrame containing processed DI futures data.
52
+
53
+ Notes:
54
+ - BDaysToExp: number of business days to ExpirationDate.
55
+ - OpenContracts: number of open contracts at the start of the trading day.
56
+ """
57
+ df_raw = cm.fetch_raw_df(asset_code="DI1", trade_date=trade_date)
58
+ if return_raw or df_raw.empty:
59
+ return df_raw
60
+ df = cm.process_raw_df(df_raw, trade_date, asset_code="DI1")
61
+ df = _process_df(df, trade_date)
62
+ return cm.reorder_columns(df)
@@ -19,7 +19,7 @@ def _convert_prices_to_rates(prices: pd.Series, n_days: pd.Series) -> pd.Series:
19
19
  return rates.round(5)
20
20
 
21
21
 
22
- def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
22
+ def _process_ddi_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
23
23
  """
24
24
  Internal function to process and transform raw DI futures data.
25
25
 
@@ -30,22 +30,8 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
30
30
  Returns:
31
31
  pd.DataFrame: Processed and transformed data as a Pandas pd.DataFrame.
32
32
  """
33
- df = cm.rename_columns(df)
34
-
35
- df["TradeDate"] = trade_date
36
- # Convert to datetime64[ns] since it is pandas default type for timestamps
37
- df["TradeDate"] = df["TradeDate"].astype("datetime64[ns]")
38
-
39
- # Contract code format was changed in 22/05/2006
40
- if trade_date < pd.Timestamp("2006-05-22"):
41
- df["ExpirationDate"] = df["ExpirationCode"].apply(
42
- cm.get_old_expiration_date, args=(trade_date,)
43
- )
44
- else:
45
- df["ExpirationDate"] = df["ExpirationCode"].apply(cm.get_expiration_date)
46
-
47
33
  df["DaysToExp"] = (df["ExpirationDate"] - trade_date).dt.days
48
- # Convert to nullable integer, since other columns use this data type
34
+ # Convert to nullable integer, since it is the default type in the library
49
35
  df["DaysToExp"] = df["DaysToExp"].astype(pd.Int64Dtype())
50
36
 
51
37
  # Remove expired contracts
@@ -53,10 +39,6 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
53
39
 
54
40
  # Columns where 0 means NaN
55
41
  rate_cols = [col for col in df.columns if "Rate" in col]
56
- cols_with_nan = rate_cols + ["SettlementPrice"]
57
- # Replace 0 with NaN in these columns
58
- df[cols_with_nan] = df[cols_with_nan].replace(0, pd.NA)
59
-
60
42
  df[rate_cols] = df[rate_cols].div(100).round(5)
61
43
 
62
44
  # Calculate SettlementRate
@@ -64,15 +46,10 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
64
46
  df["SettlementPrice"], df["DaysToExp"]
65
47
  )
66
48
 
67
- df["TickerSymbol"] = "DI1" + df["ExpirationCode"]
68
-
69
- # Filter and order columns
70
- df = cm.reorder_columns(df)
71
-
72
49
  return df
73
50
 
74
51
 
75
- def fetch_past_ddi(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
52
+ def fetch_ddi(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
76
53
  """
77
54
  Fetchs the DDI futures data for a given date from B3.
78
55
 
@@ -95,7 +72,9 @@ def fetch_past_ddi(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.Dat
95
72
  - DaysToExp: number of business days to ExpirationDate.
96
73
  - OpenContracts: number of open contracts at the start of the trading day.
97
74
  """
98
- df_raw = cm.fetch_past_raw_df(asset_code="DDI", trade_date=trade_date)
75
+ df_raw = cm.fetch_raw_df(asset_code="DDI", trade_date=trade_date)
99
76
  if return_raw or df_raw.empty:
100
77
  return df_raw
101
- return _process_raw_df(df_raw, trade_date)
78
+ df = cm.process_raw_df(df_raw, trade_date, asset_code="DDI")
79
+ df = _process_ddi_df(df, trade_date)
80
+ return cm.reorder_columns(df)
@@ -1,6 +1,6 @@
1
1
  import pandas as pd
2
2
 
3
- from .. import bday as bd
3
+ from ... import bday as bd
4
4
  from . import common as cm
5
5
 
6
6
 
@@ -31,7 +31,7 @@ def _adjust_older_contracts_rates(df: pd.DataFrame, rate_cols: list) -> pd.DataF
31
31
  return df
32
32
 
33
33
 
34
- def _process_past_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
34
+ def _process_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
35
35
  """
36
36
  Internal function to process and transform raw DI futures data.
37
37
 
@@ -42,31 +42,12 @@ def _process_past_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataF
42
42
  Returns:
43
43
  pd.DataFrame: Processed and transformed data as a Pandas pd.DataFrame.
44
44
  """
45
- df = cm.rename_columns(df)
46
-
47
- df["TradeDate"] = trade_date
48
- # Convert to datetime64[ns] since it is pandas default type for timestamps
49
- df["TradeDate"] = df["TradeDate"].astype("datetime64[ns]")
50
-
51
- # Contract code format was changed in 22/05/2006
52
- if trade_date < pd.Timestamp("2006-05-22"):
53
- df["ExpirationDate"] = df["ExpirationCode"].apply(
54
- cm.get_old_expiration_date, args=(trade_date,)
55
- )
56
- else:
57
- df["ExpirationDate"] = df["ExpirationCode"].apply(cm.get_expiration_date)
58
-
59
45
  df["BDaysToExp"] = bd.count_bdays(trade_date, df["ExpirationDate"])
60
46
 
61
47
  # Remove expired contracts
62
48
  df.query("BDaysToExp > 0", inplace=True)
63
49
 
64
- # Columns where 0 means NaN
65
50
  rate_cols = [col for col in df.columns if "Rate" in col]
66
- cols_with_nan = rate_cols + ["SettlementPrice"]
67
- # Replace 0 with NaN in these columns
68
- df[cols_with_nan] = df[cols_with_nan].replace(0, pd.NA)
69
-
70
51
  # Prior to 17/01/2002 (inclusive), prices were not converted to rates
71
52
  if trade_date > pd.Timestamp("2002-01-17"):
72
53
  # Remove % and round to 5 (3 in %) dec. places in rate columns
@@ -79,62 +60,10 @@ def _process_past_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataF
79
60
  df["SettlementPrice"], df["BDaysToExp"]
80
61
  )
81
62
 
82
- df["TickerSymbol"] = "DI1" + df["ExpirationCode"]
83
-
84
- # Filter and order columns
85
- df = cm.reorder_columns(df)
86
63
  return df
87
64
 
88
65
 
89
- def _process_last_raw_di_df(raw_df: pd.DataFrame) -> pd.DataFrame:
90
- df = raw_df.copy()
91
-
92
- # Columns to be renamed
93
- rename_columns = {
94
- "TradeTimestamp": "TradeTimestamp",
95
- "symb": "TickerSymbol",
96
- "mtrtyCode": "ExpirationDate",
97
- "BDaysToExp": "BDaysToExp",
98
- "opnCtrcts": "OpenContracts",
99
- "tradQty": "TradeCount",
100
- "traddCtrctsQty": "TradeVolume",
101
- "grssAmt": "FinancialVolume",
102
- "prvsDayAdjstmntPric": "PrevSettlementRate",
103
- "bottomLmtPric": "MinLimitRate",
104
- "topLmtPric": "MaxLimitRate",
105
- "opngPric": "OpenRate",
106
- "minPric": "MinRate",
107
- "avrgPric": "AvgRate",
108
- "maxPric": "MaxRate",
109
- "buyOffer.price": "LastAskRate",
110
- "sellOffer.price": "LastBidRate",
111
- "curPrc": "LastRate",
112
- }
113
- # Rename columns
114
- df = df.rename(columns=rename_columns)
115
-
116
- df["BDaysToExp"] = bd.count_bdays(df["TradeTimestamp"], df["ExpirationDate"])
117
-
118
- # Remove percentage in all rate columns
119
- rate_cols = [col for col in df.columns if "Rate" in col]
120
- df[rate_cols] = df[rate_cols] / 100
121
-
122
- # Reorder columns based on the order of the dictionary
123
- return df[rename_columns.values()]
124
-
125
-
126
- def fetch_last_di() -> pd.DataFrame:
127
- """
128
- Fetch the latest DI futures data from B3.
129
-
130
- Returns:
131
- pd.DataFrame: A Pandas pd.DataFrame containing the latest DI futures data.
132
- """
133
- raw_df = cm.fetch_last_raw_df(future_code="DI1")
134
- return _process_last_raw_di_df(raw_df)
135
-
136
-
137
- def fetch_past_di(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
66
+ def fetch_di(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
138
67
  """
139
68
  Fetchs the DI futures data for a given date from B3.
140
69
 
@@ -154,7 +83,9 @@ def fetch_past_di(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.Data
154
83
  - BDaysToExp: number of business days to ExpirationDate.
155
84
  - OpenContracts: number of open contracts at the start of the trading day.
156
85
  """
157
- df_raw = cm.fetch_past_raw_df(asset_code="DI1", trade_date=trade_date)
86
+ df_raw = cm.fetch_raw_df(asset_code="DI1", trade_date=trade_date)
158
87
  if return_raw or df_raw.empty:
159
88
  return df_raw
160
- return _process_past_raw_df(df_raw, trade_date)
89
+ df = cm.process_raw_df(df_raw, trade_date, asset_code="DI1")
90
+ df = _process_df(df, trade_date)
91
+ return cm.reorder_columns(df)
@@ -7,7 +7,7 @@ import requests
7
7
  from lxml import etree
8
8
  from pandas import DataFrame, Timestamp
9
9
 
10
- from .. import bday
10
+ from ... import bday
11
11
  from . import common as cm
12
12
 
13
13
 
@@ -206,7 +206,7 @@ def _standardize_column_names(df: DataFrame) -> DataFrame:
206
206
  "TradAvrgPric": "AvgRate",
207
207
  "MaxPric": "MaxRate",
208
208
  "FrstPric": "FirstRate",
209
- "LastPric": "LastRate",
209
+ "LastPric": "CloseRate",
210
210
  "AdjstdQtTax": "SettlementRate",
211
211
  # "RglrTxsQty"
212
212
  # "RglrTraddCtrcts"
@@ -3,7 +3,7 @@ import pandas as pd
3
3
  from . import common as cm
4
4
 
5
5
 
6
- def _process_past_data(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
6
+ def _process_frc_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
7
7
  """
8
8
  Internal function to process and transform raw DI futures data.
9
9
 
@@ -33,14 +33,10 @@ def _process_past_data(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFra
33
33
  # Round to 5 decimal places (3 in %) since it is the contract's precision
34
34
  df[rate_cols] = df[rate_cols].replace(0, pd.NA).div(100).round(5)
35
35
 
36
- df["TickerSymbol"] = "FRC" + df["ExpirationCode"]
37
-
38
- # Filter and order columns
39
- df = cm.reorder_columns(df)
40
36
  return df
41
37
 
42
38
 
43
- def fetch_past_frc(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
39
+ def fetch_frc(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
44
40
  """
45
41
  Fetchs the DI futures data for a given date from B3.
46
42
 
@@ -55,7 +51,10 @@ def fetch_past_frc(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.Dat
55
51
  Returns:
56
52
  pd.DataFrame: A Pandas pd.DataFrame containing processed DI futures data.
57
53
  """
58
- df_raw = cm.fetch_past_raw_df(asset_code="FRC", trade_date=trade_date)
54
+ df_raw = cm.fetch_raw_df(asset_code="FRC", trade_date=trade_date)
59
55
  if return_raw or df_raw.empty:
60
56
  return df_raw
61
- return _process_past_data(df_raw, trade_date)
57
+ # Filter and order columns
58
+ df = cm.process_raw_df(df_raw, trade_date, asset_code="FRC")
59
+ df = _process_frc_df(df, trade_date)
60
+ return cm.reorder_columns(df)
@@ -0,0 +1,3 @@
1
+ from .trading_data import fetch_intraday
2
+
3
+ __all__ = ["fetch_intraday"]
@@ -0,0 +1,107 @@
1
+ import pandas as pd
2
+ import requests
3
+
4
+
5
+ def _fetch_raw_df(future_code: str) -> pd.DataFrame:
6
+ """
7
+ Fetch the latest data for a given future code from B3 derivatives quotation API.
8
+
9
+ Args:
10
+ future_code (str): The future code to fetch data for.
11
+
12
+ Returns:
13
+ pd.DataFrame: A DataFrame containing the normalized and cleaned data from the API.
14
+
15
+ Raises:
16
+ Exception: An exception is raised if the data fetch operation fails.
17
+ """
18
+
19
+ url = f"https://cotacao.b3.com.br/mds/api/v1/DerivativeQuotation/{future_code}"
20
+
21
+ try:
22
+ r = requests.get(url)
23
+ r.raise_for_status() # Check for HTTP request errors
24
+ except requests.exceptions.RequestException:
25
+ raise Exception(f"Failed to fetch data for {future_code}.") from None
26
+
27
+ r.encoding = "utf-8" # Explicitly set response encoding to utf-8 for consistency
28
+
29
+ if "Quotation not available" in r.text:
30
+ return pd.DataFrame()
31
+
32
+ # Normalize JSON response into a flat table
33
+ df = pd.json_normalize(r.json()["Scty"])
34
+
35
+ # Clean and reformat the DataFrame columns
36
+ df.columns = (df.columns
37
+ .str.replace("SctyQtn.", "")
38
+ .str.replace("asset.AsstSummry.", "")
39
+ ) # fmt: skip
40
+ df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
41
+
42
+ # Convert maturity codes to datetime and drop rows with missing values
43
+ df["mtrtyCode"] = pd.to_datetime(df["mtrtyCode"], errors="coerce")
44
+ df.dropna(subset=["mtrtyCode"], inplace=True)
45
+
46
+ # Sort the DataFrame by maturity code and reset the index
47
+ df.sort_values("mtrtyCode", inplace=True, ignore_index=True)
48
+
49
+ # Get current date and time
50
+ now = pd.Timestamp.now().round("s")
51
+ # Subtract 15 minutes from the current time to account for API delay
52
+ trade_ts = now - pd.Timedelta(minutes=15)
53
+ df["TradeTimestamp"] = trade_ts
54
+
55
+ # Convert DataFrame to use nullable data types for better type consistency
56
+ df = df.convert_dtypes(dtype_backend="numpy_nullable")
57
+
58
+ return df
59
+
60
+
61
+ def _process_raw_df(raw_df: pd.DataFrame) -> pd.DataFrame:
62
+ df = raw_df.copy()
63
+
64
+ # Columns to be renamed
65
+ all_columns = {
66
+ "TradeTimestamp": "TradeTimestamp",
67
+ "symb": "TickerSymbol",
68
+ "mtrtyCode": "ExpirationDate",
69
+ "BDaysToExp": "BDaysToExp",
70
+ "opnCtrcts": "OpenContracts",
71
+ "tradQty": "TradeCount",
72
+ "traddCtrctsQty": "TradeVolume",
73
+ "grssAmt": "FinancialVolume",
74
+ "prvsDayAdjstmntPric": "PrevSettlementRate",
75
+ "bottomLmtPric": "MinLimitRate",
76
+ "topLmtPric": "MaxLimitRate",
77
+ "opngPric": "OpenRate",
78
+ "minPric": "MinRate",
79
+ "avrgPric": "AvgRate",
80
+ "maxPric": "MaxRate",
81
+ "buyOffer.price": "CurrentAskRate",
82
+ "sellOffer.price": "CurrentBidRate",
83
+ "curPrc": "CurrentRate",
84
+ }
85
+ # Check which columns are present in the DataFrame before renaming
86
+ rename_dict = {c: all_columns[c] for c in all_columns if c in df.columns}
87
+ df = df.rename(columns=rename_dict)
88
+
89
+ # df["BDaysToExp"] = bd.count_bdays(df["TradeTimestamp"], df["ExpirationDate"])
90
+
91
+ # Remove percentage in all rate columns
92
+ rate_cols = [col for col in df.columns if "Rate" in col]
93
+ df[rate_cols] = df[rate_cols] / 100
94
+
95
+ # Reorder columns based on the order of the dictionary
96
+ return df[rename_dict.values()]
97
+
98
+
99
+ def fetch_intraday(future_code: str) -> pd.DataFrame:
100
+ """
101
+ Fetch the latest DI futures data from B3.
102
+
103
+ Returns:
104
+ pd.DataFrame: A Pandas pd.DataFrame containing the latest DI futures data.
105
+ """
106
+ raw_df = _fetch_raw_df(future_code)
107
+ return _process_raw_df(raw_df)
@@ -3,7 +3,7 @@ import io
3
3
  import pandas as pd
4
4
  import requests
5
5
 
6
- from . import futures as ft
6
+ from .futures import historical as fh
7
7
 
8
8
  # URL Constants
9
9
  ANBIMA_NON_MEMBER_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs/"
@@ -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.fetch_past_di(reference_date)[["ExpirationDate", "SettlementRate"]]
153
+ df_di = fh.fetch_di(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)
@@ -23,16 +23,16 @@ classifiers = [
23
23
  "Topic :: Office/Business :: Financial :: Investment",
24
24
  ]
25
25
  dependencies = [
26
- "requests",
27
26
  "pandas>=2.0.0",
28
27
  "numpy",
29
28
  "beautifulsoup4",
30
29
  "html5lib",
31
30
  "lxml",
32
31
  "python-calamine>=0.2.0",
32
+ "requests>=2.31.0",
33
33
  ]
34
34
  dynamic = []
35
- version = "0.7.7"
35
+ version = "0.7.8"
36
36
 
37
37
  [project.license]
38
38
  file = "LICENSE"
File without changes
@@ -0,0 +1,38 @@
1
+ import pyield as yd
2
+
3
+
4
+ def test_settlement_rate_with_old_holiday_list():
5
+ settlement_rates = {
6
+ "DI1N27": 0.09809,
7
+ "DI1F33": 0.10368,
8
+ }
9
+
10
+ # 22-12-2023 is before the new holiday calendar
11
+ df = yd.fetch_asset(asset_code="DI1", reference_date="2023-12-22")
12
+ tickers = list(settlement_rates.keys()) # noqa: F841
13
+ result = df.query("TickerSymbol in @tickers")["SettlementRate"].to_list()
14
+ assert result == list(settlement_rates.values())
15
+
16
+
17
+ def test_settlement_rates_with_current_holiday_list():
18
+ settlement_rates = {
19
+ "DI1F24": 0.11644,
20
+ "DI1J24": 0.11300,
21
+ "DI1N24": 0.10786,
22
+ "DI1V24": 0.10321,
23
+ "DI1F25": 0.10031,
24
+ "DI1J25": 0.09852,
25
+ "DI1N25": 0.09715,
26
+ "DI1V25": 0.09651,
27
+ "DI1F26": 0.09583,
28
+ "DI1N26": 0.09631,
29
+ "DI1F27": 0.09683,
30
+ "DI1N27": 0.09794,
31
+ "DI1F29": 0.10042,
32
+ "DI1F31": 0.10240,
33
+ "DI1F33": 0.10331,
34
+ }
35
+ df = yd.fetch_asset(asset_code="DI1", reference_date="2023-12-26")
36
+ tickers = list(settlement_rates.keys()) # noqa: F841
37
+ results = df.query("TickerSymbol in @tickers")["SettlementRate"].to_list()
38
+ assert results == list(settlement_rates.values())
@@ -1 +0,0 @@
1
- __version__ = "0.7.7"
@@ -1,13 +0,0 @@
1
- from .common import get_expiration_date, get_old_expiration_date
2
- from .ddi import fetch_past_ddi
3
- from .di import fetch_last_di, fetch_past_di
4
- from .frc import fetch_past_frc
5
-
6
- __all__ = [
7
- "fetch_past_di",
8
- "fetch_last_di",
9
- "fetch_past_ddi",
10
- "fetch_past_frc",
11
- "get_expiration_date",
12
- "get_old_expiration_date",
13
- ]
@@ -1,80 +0,0 @@
1
- import pandas as pd
2
-
3
- from pyield import futures as ft
4
-
5
-
6
- def test_valid_old_contract_code1():
7
- expiration_code = "JAN3" # Valid contract code
8
- trade_date = pd.Timestamp("2001-05-21")
9
- result = ft.get_old_expiration_date(expiration_code, trade_date)
10
- contract_expiration = pd.Timestamp("2003-01-02")
11
- assert result == contract_expiration
12
-
13
-
14
- def test_valid_old_contract_code2():
15
- expiration_code = "JAN3" # Valid contract code
16
- trade_date = pd.Timestamp("1990-01-01")
17
- result = ft.get_old_expiration_date(expiration_code, trade_date)
18
- contract_expiration = pd.Timestamp("1993-01-04")
19
- assert result == contract_expiration
20
-
21
-
22
- def test_invalid_old_contract_code():
23
- expiration_code = "J3" # Invalid contract code
24
- trade_date = pd.Timestamp("2001-01-02")
25
- # Must return NaT
26
- result = ft.get_old_expiration_date(expiration_code, trade_date)
27
- assert pd.isnull(result)
28
-
29
-
30
- def test_new_contract_code():
31
- expiration_code = "F23" # Valid contract code
32
- result = ft.get_expiration_date(expiration_code)
33
- contract_expiration = pd.Timestamp("2023-01-02")
34
- assert result == contract_expiration
35
-
36
-
37
- def test_settlement_rate_with_old_holiday_list():
38
- settlement_rates = {
39
- "DI1N27": 0.09809,
40
- "DI1F33": 0.10368,
41
- }
42
-
43
- # 22-12-2023 is before the new holiday calendar
44
- test_date = pd.Timestamp("2023-12-22")
45
- df = ft.fetch_past_di(trade_date=test_date)
46
- tickers = list(settlement_rates.keys()) # noqa: F841
47
- result = df.query("TickerSymbol in @tickers")["SettlementRate"].to_list()
48
- assert result == list(settlement_rates.values())
49
-
50
-
51
- def test_settlement_rates_with_current_holiday_list():
52
- settlement_rates = {
53
- "DI1F24": 0.11644,
54
- "DI1J24": 0.11300,
55
- "DI1N24": 0.10786,
56
- "DI1V24": 0.10321,
57
- "DI1F25": 0.10031,
58
- "DI1J25": 0.09852,
59
- "DI1N25": 0.09715,
60
- "DI1V25": 0.09651,
61
- "DI1F26": 0.09583,
62
- "DI1N26": 0.09631,
63
- "DI1F27": 0.09683,
64
- "DI1N27": 0.09794,
65
- "DI1F29": 0.10042,
66
- "DI1F31": 0.10240,
67
- "DI1F33": 0.10331,
68
- }
69
- test_date = pd.Timestamp("2023-12-26")
70
- df = ft.fetch_past_di(trade_date=test_date)
71
- tickers = list(settlement_rates.keys()) # noqa: F841
72
- results = df.query("TickerSymbol in @tickers")["SettlementRate"].to_list()
73
- assert results == list(settlement_rates.values())
74
-
75
-
76
- def test_non_business_day():
77
- non_business_day = pd.Timestamp("2023-12-24")
78
- # Test if it an empty DataFrame is returned
79
- df = ft.fetch_past_di(trade_date=non_business_day)
80
- assert df.empty
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