PYield 0.7.2__tar.gz → 0.7.4__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 (30) hide show
  1. {pyield-0.7.2 → pyield-0.7.4}/PKG-INFO +9 -8
  2. {pyield-0.7.2 → pyield-0.7.4}/README.md +7 -7
  3. pyield-0.7.4/pyield/__about__.py +1 -0
  4. {pyield-0.7.2 → pyield-0.7.4}/pyield/__init__.py +2 -1
  5. pyield-0.7.4/pyield/data_access.py +148 -0
  6. pyield-0.7.4/pyield/futures/__init__.py +13 -0
  7. {pyield-0.7.2 → pyield-0.7.4}/pyield/futures/common.py +58 -4
  8. {pyield-0.7.2 → pyield-0.7.4}/pyield/futures/ddi.py +6 -6
  9. {pyield-0.7.2 → pyield-0.7.4}/pyield/futures/di.py +80 -35
  10. {pyield-0.7.2 → pyield-0.7.4}/pyield/futures/di_xml.py +4 -4
  11. pyield-0.7.4/pyield/futures/frc.py +94 -0
  12. {pyield-0.7.2 → pyield-0.7.4}/pyield/indicators.py +58 -4
  13. pyield-0.7.4/pyield/projections.py +87 -0
  14. {pyield-0.7.2 → pyield-0.7.4}/pyield/treasuries.py +1 -1
  15. {pyield-0.7.2 → pyield-0.7.4}/pyield/utils.py +13 -13
  16. {pyield-0.7.2 → pyield-0.7.4}/pyproject.toml +2 -1
  17. {pyield-0.7.2 → pyield-0.7.4}/tests/test_futures.py +24 -26
  18. pyield-0.7.2/pyield/__about__.py +0 -1
  19. pyield-0.7.2/pyield/data_access.py +0 -89
  20. pyield-0.7.2/pyield/futures/__init__.py +0 -10
  21. {pyield-0.7.2 → pyield-0.7.4}/LICENSE +0 -0
  22. {pyield-0.7.2 → pyield-0.7.4}/pyield/bday.py +0 -0
  23. {pyield-0.7.2 → pyield-0.7.4}/pyield/data_analysis.py +0 -0
  24. {pyield-0.7.2 → pyield-0.7.4}/pyield/holidays/__init__.py +0 -0
  25. {pyield-0.7.2 → pyield-0.7.4}/pyield/holidays/br_holidays_new.txt +0 -0
  26. {pyield-0.7.2 → pyield-0.7.4}/pyield/holidays/br_holidays_old.txt +0 -0
  27. {pyield-0.7.2 → pyield-0.7.4}/pyield/holidays/core.py +0 -0
  28. {pyield-0.7.2 → pyield-0.7.4}/pyield/py.typed +0 -0
  29. {pyield-0.7.2 → pyield-0.7.4}/tests/__init__.py +0 -0
  30. {pyield-0.7.2 → pyield-0.7.4}/tests/test_bday.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PYield
3
- Version: 0.7.2
3
+ Version: 0.7.4
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>
@@ -38,6 +38,7 @@ Requires-Dist: numpy
38
38
  Requires-Dist: beautifulsoup4
39
39
  Requires-Dist: html5lib
40
40
  Requires-Dist: lxml
41
+ Requires-Dist: python-calamine>=0.2.0
41
42
  Description-Content-Type: text/markdown
42
43
 
43
44
  [![PyPI version](https://img.shields.io/pypi/v/pyield.svg)](https://pypi.python.org/pypi/pyield)
@@ -101,14 +102,14 @@ dtype: datetime64[ns]
101
102
  # Fetch a DataFrame with the DI Futures data from B3
102
103
  >>> yd.fetch_asset(asset_code="DI1", reference_date='2024-03-08')
103
104
 
104
- TradeDate ExpirationCode ExpirationDate BDaysToExpiration ... LastRate LastAskRate LastBidRate SettlementRate
105
- 2024-03-08 J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
106
- 2024-03-08 K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
107
- 2024-03-08 M24 2024-06-03 58 ... 10.604 10.602 10.604 10.608
105
+ TradeDate TickerSymbol ExpirationDate BDaysToExp ... LastRate LastAskRate LastBidRate SettlementRate
106
+ 2024-03-08 DI1J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
107
+ 2024-03-08 DI1K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
108
+ 2024-03-08 DI1M24 2024-06-03 58 ... 10.604 10.602 10.604 10.608
108
109
  ... ... ... ... ... ... ... ... ...
109
- 2024-03-08 F37 2037-01-02 3213 ... <NA> <NA> <NA> 10.859
110
- 2024-03-08 F38 2038-01-04 3462 ... <NA> <NA> <NA> 10.859
111
- 2024-03-08 F39 2039-01-03 3713 ... <NA> <NA> <NA> 10.85
110
+ 2024-03-08 DI1F37 2037-01-02 3213 ... <NA> <NA> <NA> 10.859
111
+ 2024-03-08 DI1F38 2038-01-04 3462 ... <NA> <NA> <NA> 10.859
112
+ 2024-03-08 DI1F39 2039-01-03 3713 ... <NA> <NA> <NA> 10.85
112
113
  ```
113
114
 
114
115
  ### 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 ExpirationCode ExpirationDate BDaysToExpiration ... LastRate LastAskRate LastBidRate SettlementRate
63
- 2024-03-08 J24 2024-04-01 15 ... 10.952 10.952 10.956 10.956
64
- 2024-03-08 K24 2024-05-02 37 ... 10.776 10.774 10.780 10.777
65
- 2024-03-08 M24 2024-06-03 58 ... 10.604 10.602 10.604 10.608
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 F37 2037-01-02 3213 ... <NA> <NA> <NA> 10.859
68
- 2024-03-08 F38 2038-01-04 3462 ... <NA> <NA> <NA> 10.859
69
- 2024-03-08 F39 2039-01-03 3713 ... <NA> <NA> <NA> 10.85
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.4"
@@ -1,6 +1,6 @@
1
1
  from .__about__ import __version__
2
2
  from .bday import count_bdays, generate_bdays, is_bday, offset_bdays
3
- from .data_access import fetch_asset, fetch_indicator
3
+ from .data_access import fetch_asset, fetch_indicator, fetch_projection
4
4
  from .data_analysis import calculate_spreads
5
5
 
6
6
  __all__ = [
@@ -11,5 +11,6 @@ __all__ = [
11
11
  "count_bdays",
12
12
  "fetch_asset",
13
13
  "fetch_indicator",
14
+ "fetch_projection",
14
15
  "calculate_spreads",
15
16
  ]
@@ -0,0 +1,148 @@
1
+ import pandas as pd
2
+
3
+ from . import futures as ft
4
+ from . import indicators as it
5
+ from . import projections as pr
6
+ from . import treasuries as tr
7
+ from .utils import _normalize_date
8
+
9
+
10
+ def fetch_asset(
11
+ asset_code: str,
12
+ reference_date: str | pd.Timestamp | None = None,
13
+ **kwargs,
14
+ ) -> pd.DataFrame:
15
+ """
16
+ Fetches data for a specified asset based on type and reference date.
17
+
18
+ Args:
19
+ asset_code (str): The asset code identifying the type of financial asset.
20
+ Supported options:
21
+ - "TRB": Treasury bonds (indicative rates from ANBIMA).
22
+ - "LTN", "LFT", "NTN-F", "NTN-B": Specific types of Brazilian treasury bonds
23
+ (indicative rates from ANBIMA).
24
+ - "DI1": One-day Interbank Deposit Futures (Futuro de DI) from B3.
25
+ - "DDI": DI x U.S. Dollar Spread Futures (Futuro de Cupom Cambial) from B3.
26
+ - "FRC": Forward Rate Agreement (FRA) from B3.
27
+ reference_date (str | pd.Timestamp | None): The reference date for which data is
28
+ fetched. Defaults to the last business day if None.
29
+ **kwargs: Additional keyword arguments, specifically:
30
+ - return_raw (bool): Whether to return raw data without processing. Defaults
31
+ to False.
32
+
33
+ Returns:
34
+ pd.DataFrame: A DataFrame containing the fetched data for the specified asset.
35
+
36
+ Raises:
37
+ ValueError: If the asset code is not recognized or supported.
38
+
39
+ Examples:
40
+ >>> fetch_asset('TRB', '2023-04-01')
41
+ >>> fetch_asset('DI1', '2023-04-01', return_raw=True)
42
+ """
43
+ return_raw = kwargs.get("return_raw", False)
44
+ normalized_date = _normalize_date(reference_date)
45
+
46
+ if asset_code.lower() == "trb":
47
+ return tr.fetch_bonds(reference_date=normalized_date, return_raw=return_raw)
48
+ elif asset_code.lower() in ["ltn", "lft", "ntn-f", "ntn-b"]:
49
+ df = tr.fetch_bonds(reference_date=normalized_date)
50
+ return df.query(f"BondType == '{asset_code.upper()}'")
51
+
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.")
64
+
65
+
66
+ def fetch_indicator(
67
+ indicator_code: str,
68
+ reference_date: str | pd.Timestamp | None = None,
69
+ ) -> float | None:
70
+ """
71
+ Fetches data for a specified economic indicator and reference date.
72
+
73
+ Args:
74
+ indicator_code (str): The code for the economic indicator. Supported options:
75
+ - "SELIC": SELIC target rate from the Central Bank of Brazil, expressed as
76
+ an annual rate (p.a.).
77
+ - "DI": Interbank Deposit rate (DI) from B3 expressed as a daily rate,
78
+ calculated on a daily basis and expressed per diem (p.d.).
79
+ - "IPCA": IPCA monthly inflation rate from IBGE, expressed per month (p.m.).
80
+ - "VNA_LFT": VNA (Valor Nominal Atualizado) of LFT (Letra Financeira do
81
+ Tesouro), which reflects updated nominal values for these bonds.
82
+ - reference_date (str | pd.Timestamp | None): The reference date for which data
83
+ is fetched. Defaults to the last business day if None.
84
+
85
+ Returns:
86
+ float | None: The value of the specified economic indicator for the reference
87
+ date. Returns None if data is not found.
88
+
89
+ Raises:
90
+ ValueError: If the indicator code is not recognized or supported.
91
+
92
+ Examples:
93
+ >>> fetch_indicator('SELIC', '2023-04-01')
94
+ 0.1075 # Indicates a SELIC target rate of 10.75% p.a.
95
+ >>> fetch_indicator('IPCA', '2023-03-10')
96
+ 0.0016 # Indicates an IPCA monthly rate of 0.16% p.m.
97
+ >>> fetch_indicator('DI', '2023-04-17')
98
+ 0.00040168 # Indicates a DI daily rate of 0.02% p.d.
99
+ """
100
+ normalized_date = _normalize_date(reference_date)
101
+
102
+ if indicator_code.lower() == "selic":
103
+ return it.fetch_selic_target(reference_date=normalized_date)
104
+ elif indicator_code.lower() == "ipca":
105
+ return it.fetch_ipca_mr(reference_date=normalized_date)
106
+ elif indicator_code.lower() == "di":
107
+ return it.fetch_di(reference_date=normalized_date)
108
+ elif indicator_code.lower() == "vna_lft":
109
+ return it.fetch_vna_selic(reference_date=normalized_date)
110
+ else:
111
+ raise ValueError("Indicator type not supported.")
112
+
113
+
114
+ def fetch_projection(projection_code: str) -> dict:
115
+ """
116
+ Fetches a financial projection for a specified code and reference date.
117
+
118
+ Args:
119
+ projection_code (str): The code for the financial projection. Supported options:
120
+ - "IPCA_CM": IPCA projection for the current month from ANBIMA.
121
+
122
+ Returns:
123
+ IndicatorProjection: An instance of IndicatorProjection containing:
124
+ - last_updated (pd.Timestamp): The datetime when the projection was last
125
+ updated.
126
+ - reference_month_ts (pd.Timestamp): The month to which the projection
127
+ applies.
128
+ - reference_month_br (str): The formatted month as a string formatted using
129
+ the pt_BR locale.
130
+ - projected_value (float): The projected indicator value.
131
+
132
+ Raises:
133
+ ValueError: If the projection code is not recognized or supported.
134
+
135
+ Examples:
136
+ >>> fetch_projection('IPCA_CM')
137
+ IndicatorProjection(
138
+ last_updated=pd.Timestamp('2024-04-19 18:55:00'),
139
+ reference_month_ts=pd.Timestamp('2024-04-01 00:00:00'),
140
+ reference_month_br='ABR/2024',
141
+ projected_value=0.35
142
+ )
143
+ """
144
+
145
+ if projection_code.lower() == "ipca_cm":
146
+ return pr.fetch_current_month_ipca_projection()
147
+ else:
148
+ raise ValueError("Projection type not supported.")
@@ -0,0 +1,13 @@
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
+ ]
@@ -131,12 +131,12 @@ def get_old_expiration_date(
131
131
  return pd.NaT # type: ignore
132
132
 
133
133
 
134
- def _fetch_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
134
+ def fetch_past_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
135
135
  """
136
- Internal function to fetch raw DI futures data from B3 for a specific trade date.
136
+ Fetch the historical futures data from B3 for a specific trade date.
137
137
 
138
138
  Args:
139
- trade_date: a datetime-like object representing the 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.
@@ -170,6 +170,60 @@ def _fetch_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
170
170
  df["VAR. PTOS."] = df["VAR. PTOS."].astype(pd.StringDtype())
171
171
 
172
172
  # Force "AJUSTE CORRIG. (4)" to be float, since it can be also read as int
173
- df["AJUSTE CORRIG. (4)"] = df["AJUSTE CORRIG. (4)"].astype(pd.Float64Dtype())
173
+ if "AJUSTE CORRIG. (4)" in df.columns:
174
+ df["AJUSTE CORRIG. (4)"] = df["AJUSTE CORRIG. (4)"].astype(pd.Float64Dtype())
175
+
176
+ return df
177
+
178
+
179
+ def fetch_last_raw_df(future_code: str) -> pd.DataFrame:
180
+ """
181
+ Fetch the latest data for a given future code from B3 derivatives quotation API.
182
+
183
+ Args:
184
+ future_code (str): The future code to fetch data for.
185
+
186
+ Returns:
187
+ pd.DataFrame: A DataFrame containing the normalized and cleaned data from the API.
188
+
189
+ Raises:
190
+ Exception: An exception is raised if the data fetch operation fails.
191
+ """
192
+
193
+ url = f"https://cotacao.b3.com.br/mds/api/v1/DerivativeQuotation/{future_code}"
194
+
195
+ try:
196
+ r = requests.get(url)
197
+ r.raise_for_status() # Check for HTTP request errors
198
+ except requests.exceptions.RequestException:
199
+ raise Exception(f"Failed to fetch data for {future_code}.") from None
200
+
201
+ r.encoding = "utf-8" # Explicitly set response encoding to utf-8 for consistency
202
+
203
+ # Normalize JSON response into a flat table
204
+ df = pd.json_normalize(r.json()["Scty"])
205
+
206
+ # Clean and reformat the DataFrame columns
207
+ df.columns = (df.columns
208
+ .str.replace("SctyQtn.", "")
209
+ .str.replace("asset.AsstSummry.", "")
210
+ ) # fmt: skip
211
+ df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
212
+
213
+ # Convert maturity codes to datetime and drop rows with missing values
214
+ df["mtrtyCode"] = pd.to_datetime(df["mtrtyCode"], errors="coerce")
215
+ df.dropna(subset=["mtrtyCode"], inplace=True)
216
+
217
+ # Sort the DataFrame by maturity code and reset the index
218
+ df.sort_values("mtrtyCode", inplace=True, ignore_index=True)
219
+
220
+ # Get current date and time
221
+ now = pd.Timestamp.now().round("s")
222
+ # Subtract 15 minutes from the current time to account for API delay
223
+ trade_ts = now - pd.Timedelta(minutes=15)
224
+ df["TradeTimestamp"] = trade_ts
225
+
226
+ # Convert DataFrame to use nullable data types for better type consistency
227
+ df = df.convert_dtypes(dtype_backend="numpy_nullable")
174
228
 
175
229
  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.": "NumOfTrades",
63
- "CONTR. NEGOC.": "TradedQuantity",
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
- "NumOfTrades",
137
- "TradedQuantity",
136
+ "TradeCount",
137
+ "TradeVolume",
138
138
  "FinancialVolume",
139
139
  "SettlementPrice",
140
140
  "MinRate",
@@ -149,7 +149,7 @@ def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
149
149
  return df[ordered_cols]
150
150
 
151
151
 
152
- def fetch_ddi(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
152
+ def fetch_past_ddi(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
153
153
  """
154
154
  Fetchs the DDI futures data for a given date from B3.
155
155
 
@@ -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._fetch_raw_df(asset_code="DDI", trade_date=trade_date)
175
+ df_raw = common.fetch_past_raw_df(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
- "LastRate",
32
- "LastBidRate",
33
- "LastAskRate",
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["BDaysToExpiration"])
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 _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
44
+ def _process_past_raw_df(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.": "NumOfTrades",
64
- "CONTR. NEGOC.": "TradedQuantity",
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": "LastRate",
73
+ "ÚLT. PREÇO": "CloseRate",
74
74
  "VAR. PTOS.": "PointsVariation",
75
75
  # Attention: bid/ask rates are inverted
76
- "ÚLT.OF. COMPRA": "LastAskRate",
77
- "ÚLT.OF. VENDA": "LastBidRate",
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["BDaysToExpiration"] = bday.count_bdays(trade_date, df["ExpirationDate"])
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("BDaysToExpiration > 0", inplace=True)
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
- "LastRate",
108
- "LastBidRate",
109
- "LastAskRate",
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["BDaysToExpiration"]
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,83 @@ 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
- "ExpirationCode",
131
+ "TickerSymbol",
132
+ # "ExpirationCode",
132
133
  "ExpirationDate",
133
- "BDaysToExpiration",
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
- "NumOfTrades",
138
- "TradedQuantity",
138
+ "TradeCount",
139
+ "TradeVolume",
139
140
  "FinancialVolume",
140
141
  "SettlementPrice",
142
+ "SettlementRate",
143
+ "FirstRate",
141
144
  "MinRate",
142
145
  "AvgRate",
143
146
  "MaxRate",
144
- "FirstRate",
145
- "LastRate",
146
- "LastAskRate",
147
- "LastBidRate",
148
- "SettlementRate",
147
+ "CloseAskRate",
148
+ "CloseBidRate",
149
+ "CloseRate",
149
150
  ]
150
151
  return df[ordered_cols]
151
152
 
152
153
 
153
- def fetch_di(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
154
+ def _process_last_raw_di_df(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() -> 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_raw_df(future_code="DI1")
199
+ return _process_last_raw_di_df(raw_df)
200
+
201
+
202
+ def fetch_past_di(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
154
203
  """
155
204
  Fetchs the DI futures data for a given date from B3.
156
205
 
@@ -165,15 +214,11 @@ def fetch_di(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame
165
214
  Returns:
166
215
  pd.DataFrame: A Pandas pd.DataFrame containing processed DI futures data.
167
216
 
168
- Examples:
169
- >>> from pyield.futures import di
170
- >>> di.fetch_di(pd.Timestamp("2021-01-04"))
171
-
172
217
  Notes:
173
- - BDaysToExpiration: number of business days to ExpirationDate.
218
+ - BDaysToExp: number of business days to ExpirationDate.
174
219
  - OpenContracts: number of open contracts at the start of the trading day.
175
220
  """
176
- df_raw = common._fetch_raw_df(asset_code="DI1", trade_date=trade_date)
221
+ df_raw = common.fetch_past_raw_df(asset_code="DI1", trade_date=trade_date)
177
222
  if return_raw:
178
223
  return df_raw
179
- return _process_raw_df(df_raw, trade_date)
224
+ return _process_past_raw_df(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": "TradedQuantity",
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, "BDaysToExpiration", business_days)
236
+ df.insert(3, "BDaysToExp", business_days)
237
237
 
238
238
  # Convert to nullable integer, since other columns use this data type
239
- df["BDaysToExpiration"] = df["BDaysToExpiration"].astype(pd.Int64Dtype())
239
+ df["BDaysToExp"] = df["BDaysToExp"].astype(pd.Int64Dtype())
240
240
 
241
241
  # Remove expired contracts
242
- df.query("BDaysToExpiration > 0", inplace=True)
242
+ df.query("BDaysToExp > 0", inplace=True)
243
243
 
244
244
  return df.sort_values(by=["ExpirationDate"], ignore_index=True)
245
245
 
@@ -0,0 +1,94 @@
1
+ import pandas as pd
2
+
3
+ from . import common
4
+
5
+
6
+ def _process_past_data(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
7
+ """
8
+ Internal function to process and transform raw DI futures data.
9
+
10
+ Args:
11
+ df (pd.DataFrame): the raw DI DataFrame.
12
+ trade_date: a datetime-like object representing the trade date.
13
+
14
+ Returns:
15
+ pd.DataFrame: Processed and transformed data as a Pandas pd.DataFrame.
16
+ """
17
+ # Check if the pd.DataFrame is empty
18
+ if df.empty:
19
+ return df
20
+
21
+ rename_dict = {
22
+ "VENCTO": "ExpirationCode",
23
+ "NÚM. NEGOC.": "TradeCount",
24
+ "CONTR. NEGOC.": "TradeVolume",
25
+ "VOL.": "FinancialVolume",
26
+ "AJUSTE DE REF.": "SettlementRate",
27
+ "PREÇO ABERTU.": "FirstRate",
28
+ "PREÇO MÍN.": "MinRate",
29
+ "PREÇO MÉD.": "AvgRate",
30
+ "PREÇO MÁX.": "MaxRate",
31
+ "ÚLT. PREÇO": "CloseRate",
32
+ "VAR. PTOS.": "PointsVariation",
33
+ # Attention: bid/ask rates are inverted
34
+ "ÚLT.OF. COMPRA": "CloseAskRate",
35
+ "ÚLT.OF. VENDA": "CloseBidRate",
36
+ }
37
+
38
+ df = df.rename(columns=rename_dict)
39
+
40
+ df["TradeDate"] = trade_date
41
+ # Convert to datetime64[ns] since it is pandas default type for timestamps
42
+ df["TradeDate"] = df["TradeDate"].astype("datetime64[ns]")
43
+
44
+ df["ExpirationDate"] = df["ExpirationCode"].apply(common.get_expiration_date)
45
+
46
+ # Replace 0 values in rate columns with pd.NA and remove percentage
47
+ rate_cols = [col for col in df.columns if "Rate" in col]
48
+ for col in rate_cols:
49
+ df[col] = df[col].replace(0, pd.NA)
50
+ # Round to 5 decimal places (3 in %) since it is the contract's precision
51
+ df[col] = (df[col] / 100).round(5)
52
+
53
+ df["TickerSymbol"] = "FRC" + df["ExpirationCode"]
54
+
55
+ # Filter and order columns
56
+ ordered_cols = [
57
+ "TradeDate",
58
+ "TickerSymbol",
59
+ # "ExpirationCode",
60
+ "ExpirationDate",
61
+ "TradeCount",
62
+ "TradeVolume",
63
+ "FinancialVolume",
64
+ "SettlementRate",
65
+ "FirstRate",
66
+ "MinRate",
67
+ "AvgRate",
68
+ "MaxRate",
69
+ "CloseAskRate",
70
+ "CloseBidRate",
71
+ "CloseRate",
72
+ ]
73
+ return df[ordered_cols]
74
+
75
+
76
+ def fetch_past_frc(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
77
+ """
78
+ Fetchs the DI futures data for a given date from B3.
79
+
80
+ This function fetches and processes the DI futures data from B3 for a specific
81
+ trade date. It's the primary external interface for accessing DI data.
82
+
83
+ Args:
84
+ trade_date (pd.Timestamp): The trade date to fetch the DI futures data.
85
+ raw (bool): If True, returns the raw data as a Pandas pd.DataFrame.
86
+ Defaults to False.
87
+
88
+ Returns:
89
+ pd.DataFrame: A Pandas pd.DataFrame containing processed DI futures data.
90
+ """
91
+ df_raw = common.fetch_past_raw_df(asset_code="FRC", trade_date=trade_date)
92
+ if return_raw:
93
+ return df_raw
94
+ return _process_past_data(df_raw, trade_date)
@@ -41,7 +41,7 @@ def fetch_ipca_mr(reference_date: pd.Timestamp) -> float | None:
41
41
  # Extract and return the IPCA monthly growth rate if data is available
42
42
  if data:
43
43
  ipca_str = data[0]["resultados"][0]["series"][0]["serie"][ipca_date]
44
- return float(ipca_str)
44
+ return round(float(ipca_str) / 100, 4)
45
45
  else:
46
46
  return None
47
47
 
@@ -52,8 +52,62 @@ def fetch_selic_target(reference_date: pd.Timestamp) -> float | None:
52
52
  api_url = f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.432/dados?formato=json&dataInicial={selic_date}&dataFinal={selic_date}"
53
53
  response = requests.get(api_url)
54
54
  response.raise_for_status()
55
- data = response.json()
56
- if data:
57
- return float(data[0]["valor"])
55
+
56
+ if selic_date in response.text:
57
+ data = response.json()
58
+ return round(float(data[0]["valor"]) / 100, 4)
58
59
  else:
59
60
  return None
61
+
62
+
63
+ def fetch_di(reference_date: pd.Timestamp) -> float | None:
64
+ # https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial=12/04/2024&dataFinal=12/04/2024
65
+ di_date = reference_date.strftime("%d/%m/%Y")
66
+ api_url = f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial={di_date}&dataFinal={di_date}"
67
+ response = requests.get(api_url)
68
+ response.raise_for_status()
69
+
70
+ if di_date in response.text:
71
+ data = response.json()
72
+ return round(float(data[0]["valor"]) / 100, 8)
73
+ else:
74
+ return None
75
+
76
+
77
+ def fetch_vna_selic(reference_date: pd.Timestamp) -> float | None:
78
+ # url example: https://www3.bcb.gov.br/novoselic/rest/arquivosDiarios/pub/download/3/20240418APC238
79
+
80
+ url_base = "https://www3.bcb.gov.br/novoselic/rest/arquivosDiarios/pub/download/3/"
81
+ url_file = f"{reference_date.strftime("%Y%m%d")}APC238"
82
+ url_vna = url_base + url_file
83
+ response = requests.get(url_vna)
84
+ response.raise_for_status()
85
+ file_text = response.text
86
+
87
+ # Finding the part that contains the table
88
+ start_of_table = file_text.find("EMISSAO")
89
+ end_of_table = file_text.find("99999999*")
90
+
91
+ # Extracting the table
92
+ table_text = file_text[start_of_table:end_of_table].strip()
93
+ table_lines = table_text.splitlines()
94
+
95
+ # Remove empty lines
96
+ table_lines = [line.strip() for line in table_lines if line.strip()]
97
+
98
+ # Remove first line
99
+ body_lines = table_lines[1:]
100
+
101
+ vnas = []
102
+ for line in body_lines:
103
+ vna_str = line.split()[-1].replace(",", ".")
104
+ vna_float = float(vna_str)
105
+ vnas.append(vna_float)
106
+
107
+ # Raise error if all values are not the same
108
+ vna_value = vnas[0]
109
+ if any(vna_value != vna for vna in vnas):
110
+ bcb_url = "https://www.bcb.gov.br/estabilidadefinanceira/selicbaixar"
111
+ raise ValueError(f"VNA values are not the same. Please check data at {bcb_url}")
112
+
113
+ return vna_value
@@ -0,0 +1,87 @@
1
+ import io
2
+ import locale
3
+ from dataclasses import dataclass
4
+
5
+ import pandas as pd
6
+ import python_calamine as pc
7
+ import requests
8
+
9
+
10
+ @dataclass
11
+ class IndicatorProjection:
12
+ last_updated: pd.Timestamp # Date and time of the last update
13
+ reference_month_ts: pd.Timestamp # Timestamp to which the projection applies
14
+ reference_month_br: str # Brazilian formatted month (e.g., "ABR/2024")
15
+ projected_value: float # Projected value
16
+
17
+
18
+ def fetch_current_month_ipca_projection() -> IndicatorProjection:
19
+ """
20
+ Fetches the current month's IPCA projection from the ANBIMA website and returns it
21
+ as an IndicatorProjection instance.
22
+
23
+ This function retrieves and parses the Excel file that contains economic indicators,
24
+ specifically looking for the IPCA projection. It extracts the date of the last
25
+ update and the IPCA projection for the reference month.
26
+
27
+ Args:
28
+ None
29
+
30
+ Returns:
31
+ IndicatorProjection: An instance of IndicatorProjection containing:
32
+ - last_updated (pd.Timestamp): The datetime when the data was last updated.
33
+ - reference_month_ts (pd.Timestamp): The month to which the IPCA projection
34
+ applies.
35
+ - reference_month_br (str): The formatted month as a string
36
+ (e.g., "ABR/2024") using the pt_BR locale.
37
+ - projected_value (float): The projected IPCA value.
38
+
39
+ Example:
40
+ >>> projection = fetch_current_month_ipca_projection()
41
+ >>> print(projection)
42
+ IndicatorProjection(
43
+ last_updated=pd.Timestamp('2024-04-19 18:55:00'),
44
+ reference_month_ts=pd.Timestamp('2024-04-01 00:00:00'),
45
+ reference_month_br='ABR/2024',
46
+ projected_value=0.35
47
+ )
48
+
49
+ Data file format example after parsing:
50
+ - ['Data e Hora da Última Atualização: 19/04/2024 - 18:55 h', '', '']
51
+ - ...
52
+ - ['IPCA1', 'Projeção (abr/24)', 0.35]
53
+ - ...
54
+ """
55
+ # Define the URL and get the data
56
+ url = "https://www.anbima.com.br/informacoes/indicadores/arqs/indicadores.xls"
57
+ response = requests.get(url)
58
+ excel_data = io.BytesIO(response.content)
59
+
60
+ # Load the workbook and select the first sheet
61
+ workbook = pc.load_workbook(excel_data)
62
+ first_sheet = workbook.sheet_names[0]
63
+ data = workbook.get_sheet_by_name(first_sheet).to_python(skip_empty_area=True)
64
+
65
+ # Extract projection update date and time from the first row
66
+ last_update_str = data[0][0].split("Atualização:")[-1].strip()
67
+ last_updated = pd.to_datetime(last_update_str, format="%d/%m/%Y - %H:%M h")
68
+
69
+ # Find the row containing the IPCA projection and extract its data
70
+ ipca_data = next(line for line in data if "IPCA1" in line)
71
+
72
+ # Convert the last element of the IPCA data row to float for the projection value
73
+ ipca_value = round(float(ipca_data[-1]) / 100, 4)
74
+
75
+ # Extract and format the reference month
76
+ month_text = ipca_data[1].split("(")[-1].split(")")[0]
77
+ locale.setlocale(locale.LC_TIME, "pt_BR.UTF-8")
78
+ ipca_month_ts = pd.to_datetime(month_text, format="%b/%y")
79
+ ipca_month_br = ipca_month_ts.strftime("%b/%Y").upper()
80
+ locale.setlocale(locale.LC_TIME, "") # Reset locale to default
81
+
82
+ return IndicatorProjection(
83
+ last_updated=last_updated,
84
+ reference_month_ts=ipca_month_ts,
85
+ reference_month_br=ipca_month_br,
86
+ projected_value=ipca_value,
87
+ )
@@ -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_di(reference_date)[["ExpirationDate", "SettlementRate"]]
153
+ df_di = ft.fetch_past_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)
@@ -3,10 +3,10 @@ import pandas as pd
3
3
  from . import bday
4
4
 
5
5
 
6
- def _normalize_date(reference_date: str | pd.Timestamp | None = None) -> pd.Timestamp:
6
+ def _normalize_date(input_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 previous business day.
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
@@ -31,25 +31,25 @@ def _normalize_date(reference_date: str | pd.Timestamp | None = None) -> pd.Time
31
31
  >>> _normalize_date(pd.Timestamp('2023-04-01 15:30'))
32
32
  >>> _normalize_date()
33
33
  """
34
- if isinstance(reference_date, str):
34
+ if isinstance(input_date, str):
35
35
  # Convert string date to Timestamp and normalize to midnight
36
- normalized_date = pd.Timestamp(reference_date).normalize()
37
- elif isinstance(reference_date, pd.Timestamp):
36
+ normalized_date = pd.Timestamp(input_date).normalize()
37
+ elif isinstance(input_date, pd.Timestamp):
38
38
  # Normalize Timestamp to midnight
39
- normalized_date = reference_date.normalize()
40
- elif reference_date is None:
41
- # If no date is provided, use the previous business day
39
+ normalized_date = input_date.normalize()
40
+ elif input_date is None:
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, -1)
43
+ normalized_date = bday.offset_bdays(dates=today, offset=0, roll="backward")
44
44
  else:
45
- raise ValueError("Invalid date format.")
45
+ raise ValueError(f"Date format not recognized: {input_date}")
46
46
 
47
+ error_date = normalized_date.strftime("%d-%m-%Y")
47
48
  # Validate that the date is not in the future
48
49
  if normalized_date > pd.Timestamp.today().normalize():
49
- raise ValueError("Reference date cannot be in the future.")
50
-
50
+ raise ValueError(f"Date {error_date} is in the future")
51
51
  # Validate that the date is a business day
52
52
  if not bday.is_bday(normalized_date):
53
- raise ValueError("Reference date must be a business day.")
53
+ raise ValueError(f"Date {error_date} is not a business day")
54
54
 
55
55
  return normalized_date
@@ -29,9 +29,10 @@ dependencies = [
29
29
  "beautifulsoup4",
30
30
  "html5lib",
31
31
  "lxml",
32
+ "python-calamine>=0.2.0",
32
33
  ]
33
34
  dynamic = []
34
- version = "0.7.2"
35
+ version = "0.7.4"
35
36
 
36
37
  [project.license]
37
38
  file = "LICENSE"
@@ -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
- "N27": 0.09809,
41
- "F33": 0.10368,
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.fetch_di(trade_date=test_date)
47
- expiration_codes = list(settlement_rates.keys()) # noqa: F841
48
- result = df.query("ExpirationCode in @expiration_codes")["SettlementRate"].to_list()
46
+ df = ft.fetch_past_di(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
- "F24": 0.11644,
55
- "J24": 0.11300,
56
- "N24": 0.10786,
57
- "V24": 0.10321,
58
- "F25": 0.10031,
59
- "J25": 0.09852,
60
- "N25": 0.09715,
61
- "V25": 0.09651,
62
- "F26": 0.09583,
63
- "N26": 0.09631,
64
- "F27": 0.09683,
65
- "N27": 0.09794,
66
- "F29": 0.10042,
67
- "F31": 0.10240,
68
- "F33": 0.10331,
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.fetch_di(trade_date=test_date)
72
- expiration_codes = list(settlement_rates.keys()) # noqa: F841
73
- results = df.query("ExpirationCode in @expiration_codes")[
74
- "SettlementRate"
75
- ].to_list()
71
+ df = ft.fetch_past_di(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.fetch_di(trade_date=non_business_day)
80
+ ft.fetch_past_di(trade_date=non_business_day)
@@ -1 +0,0 @@
1
- __version__ = "0.7.2"
@@ -1,89 +0,0 @@
1
- import pandas as pd
2
-
3
- from . import futures as ft
4
- from . import indicators as it
5
- from . import treasuries as tr
6
- from .utils import _normalize_date
7
-
8
-
9
- def fetch_asset(
10
- asset_code: str,
11
- reference_date: str | pd.Timestamp | None = None,
12
- **kwargs,
13
- ) -> pd.DataFrame:
14
- """
15
- Fetches data for a specified asset based on type and reference date.
16
-
17
- Args:
18
- asset_code (str): The asset code identifying the type of financial asset.
19
- Supported options:
20
- - "TRB": Treasury bonds (indicative rates from ANBIMA).
21
- - "LTN", "LFT", "NTN-F", "NTN-B": Specific types of Brazilian treasury bonds
22
- (indicative rates from ANBIMA).
23
- - "DI1": One-day Interbank Deposit Futures (Futuro de DI) from B3.
24
- - "DDI": DI x U.S. Dollar Spread Futures (Futuro de Cupom Cambial) from B3.
25
- reference_date (str | pd.Timestamp | None): The reference date for which data is
26
- fetched. Defaults to the previous business day if None.
27
- **kwargs: Additional keyword arguments, specifically:
28
- - return_raw (bool): Whether to return raw data without processing. Defaults
29
- to False.
30
-
31
- Returns:
32
- pd.DataFrame: A DataFrame containing the fetched data for the specified asset.
33
-
34
- Raises:
35
- ValueError: If the asset code is not recognized or supported.
36
-
37
- Examples:
38
- >>> fetch_asset('TRB', '2023-04-01')
39
- >>> fetch_asset('DI1', '2023-04-01', return_raw=True)
40
- """
41
- return_raw = kwargs.get("return_raw", False)
42
- normalized_date = _normalize_date(reference_date)
43
-
44
- if asset_code.lower() == "trb":
45
- return tr.fetch_bonds(reference_date=normalized_date, return_raw=return_raw)
46
- elif asset_code.lower() in ["ltn", "lft", "ntn-f", "ntn-b"]:
47
- df = tr.fetch_bonds(reference_date=normalized_date)
48
- return df.query(f"BondType == '{asset_code.upper()}'")
49
-
50
- elif asset_code.lower() == "di1":
51
- return ft.fetch_di(trade_date=normalized_date, return_raw=return_raw)
52
- elif asset_code.lower() == "ddi":
53
- return ft.fetch_ddi(trade_date=normalized_date, return_raw=return_raw)
54
- else:
55
- raise ValueError("Asset type not supported.")
56
-
57
-
58
- def fetch_indicator(
59
- indicator_code: str,
60
- reference_date: str | pd.Timestamp | None = None,
61
- ) -> float | None:
62
- """
63
- Fetches data for a specified economic indicator and reference date.
64
-
65
- Args:
66
- indicator_code (str): The code for the economic indicator. Supported options:
67
- - "SELIC": SELIC target rate from the Central Bank of Brazil.
68
- - "IPCA": IPCA monthly inflation rate from IBGE.
69
- reference_date (str | pd.Timestamp | None): The reference date for which data is
70
- fetched. Defaults to the previous business day if None.
71
-
72
- Returns:
73
- pd.Series: A Series containing the fetched data for the specified indicator.
74
-
75
- Raises:
76
- ValueError: If the indicator code is not recognized or supported.
77
-
78
- Examples:
79
- >>> fetch_indicator('SELIC', '2023-04-01')
80
- >>> fetch_indicator('IPCA', '2023-04-01')
81
- """
82
- normalized_date = _normalize_date(reference_date)
83
-
84
- if indicator_code.lower() == "selic":
85
- return it.fetch_selic_target(reference_date=normalized_date)
86
- elif indicator_code.lower() == "ipca":
87
- return it.fetch_ipca_mr(reference_date=normalized_date)
88
- else:
89
- raise ValueError("Indicator type not supported.")
@@ -1,10 +0,0 @@
1
- from .common import get_expiration_date, get_old_expiration_date
2
- from .ddi import fetch_ddi
3
- from .di import fetch_di
4
-
5
- __all__ = [
6
- "fetch_di",
7
- "fetch_ddi",
8
- "get_expiration_date",
9
- "get_old_expiration_date",
10
- ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes