PYield 0.8.1__tar.gz → 0.8.2__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 (32) hide show
  1. {pyield-0.8.1 → pyield-0.8.2}/PKG-INFO +1 -1
  2. pyield-0.8.2/pyield/__about__.py +1 -0
  3. {pyield-0.8.1 → pyield-0.8.2}/pyield/futures/historical.py +1 -1
  4. {pyield-0.8.1 → pyield-0.8.2}/pyield/futures/intraday.py +40 -31
  5. {pyield-0.8.1 → pyield-0.8.2}/pyield/futures/xml.py +9 -3
  6. {pyield-0.8.1 → pyield-0.8.2}/pyield/ntnb.py +44 -27
  7. {pyield-0.8.1 → pyield-0.8.2}/pyield/projections.py +1 -1
  8. {pyield-0.8.1 → pyield-0.8.2}/pyproject.toml +1 -1
  9. pyield-0.8.1/pyield/__about__.py +0 -1
  10. {pyield-0.8.1 → pyield-0.8.2}/LICENSE +0 -0
  11. {pyield-0.8.1 → pyield-0.8.2}/README.md +0 -0
  12. {pyield-0.8.1 → pyield-0.8.2}/pyield/__init__.py +0 -0
  13. {pyield-0.8.1 → pyield-0.8.2}/pyield/bday.py +0 -0
  14. {pyield-0.8.1 → pyield-0.8.2}/pyield/data_access.py +0 -0
  15. {pyield-0.8.1 → pyield-0.8.2}/pyield/date_validator.py +0 -0
  16. {pyield-0.8.1 → pyield-0.8.2}/pyield/futures/__init__.py +0 -0
  17. {pyield-0.8.1 → pyield-0.8.2}/pyield/futures/common.py +0 -0
  18. {pyield-0.8.1 → pyield-0.8.2}/pyield/holidays/__init__.py +0 -0
  19. {pyield-0.8.1 → pyield-0.8.2}/pyield/holidays/br_holidays_new.txt +0 -0
  20. {pyield-0.8.1 → pyield-0.8.2}/pyield/holidays/br_holidays_old.txt +0 -0
  21. {pyield-0.8.1 → pyield-0.8.2}/pyield/holidays/core.py +0 -0
  22. {pyield-0.8.1 → pyield-0.8.2}/pyield/indicators.py +0 -0
  23. {pyield-0.8.1 → pyield-0.8.2}/pyield/interpolator.py +0 -0
  24. {pyield-0.8.1 → pyield-0.8.2}/pyield/py.typed +0 -0
  25. {pyield-0.8.1 → pyield-0.8.2}/pyield/spreads.py +0 -0
  26. {pyield-0.8.1 → pyield-0.8.2}/pyield/tn_bonds.py +0 -0
  27. {pyield-0.8.1 → pyield-0.8.2}/tests/__init__.py +0 -0
  28. {pyield-0.8.1 → pyield-0.8.2}/tests/data/SPRD231222.zip +0 -0
  29. {pyield-0.8.1 → pyield-0.8.2}/tests/data/SPRD240426.zip +0 -0
  30. {pyield-0.8.1 → pyield-0.8.2}/tests/test_bday.py +0 -0
  31. {pyield-0.8.1 → pyield-0.8.2}/tests/test_futures.py +0 -0
  32. {pyield-0.8.1 → pyield-0.8.2}/tests/test_futures_xml.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PYield
3
- Version: 0.8.1
3
+ Version: 0.8.2
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>
@@ -0,0 +1 @@
1
+ __version__ = "0.8.2"
@@ -119,7 +119,7 @@ def fetch_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
119
119
  url_date = trade_date.strftime("%d/%m/%Y")
120
120
  # url example: https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data=05/10/2023&Mercadoria=DI1
121
121
  url = f"https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data={url_date}&Mercadoria={asset_code}&XLS=false"
122
- r = requests.get(url)
122
+ r = requests.get(url, timeout=10)
123
123
 
124
124
  text = r.text
125
125
  if "VENCTO" not in text:
@@ -1,10 +1,20 @@
1
+ from io import StringIO
2
+
1
3
  import pandas as pd
2
4
  import requests
3
5
 
4
6
  from .. import bday
5
7
 
6
8
 
7
- def _fetch_raw_df(future_code: str) -> pd.DataFrame:
9
+ # Função para salvar DataFrame em CSV e ler com read_csv
10
+ def convert_with_read_csv(df):
11
+ buffer = StringIO()
12
+ df.to_csv(buffer, index=False)
13
+ buffer.seek(0) # Reposiciona o cursor no início do buffer
14
+ return pd.read_csv(buffer, dtype_backend="numpy_nullable")
15
+
16
+
17
+ def _fetch_b3_df(future_code: str) -> pd.DataFrame:
8
18
  """
9
19
  Fetch the latest data for a given future code from B3 derivatives quotation API.
10
20
 
@@ -34,34 +44,8 @@ def _fetch_raw_df(future_code: str) -> pd.DataFrame:
34
44
  # Normalize JSON response into a flat table
35
45
  df = pd.json_normalize(r.json()["Scty"])
36
46
 
37
- # Clean and reformat the DataFrame columns
38
- df.columns = (df.columns
39
- .str.replace("SctyQtn.", "")
40
- .str.replace("asset.AsstSummry.", "")
41
- ) # fmt: skip
42
- df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
43
-
44
- # Convert maturity codes to datetime and drop rows with missing values
45
- df["mtrtyCode"] = pd.to_datetime(df["mtrtyCode"], errors="coerce")
46
- df.dropna(subset=["mtrtyCode"], inplace=True)
47
-
48
- # Sort the DataFrame by maturity code and reset the index
49
- df.sort_values("mtrtyCode", inplace=True, ignore_index=True)
50
-
51
- # Get currante date
52
- today = pd.Timestamp.now().normalize()
53
- df["TradeDate"] = today
54
-
55
- # Get current date and time
56
- now = pd.Timestamp.now().round("s")
57
- # Subtract 15 minutes from the current time to account for API delay
58
- trade_ts = now - pd.Timedelta(minutes=15)
59
- df["TradeTime"] = trade_ts
60
-
61
47
  # Convert DataFrame to use nullable data types for better type consistency
62
- df = df.convert_dtypes(dtype_backend="numpy_nullable")
63
-
64
- return df
48
+ return convert_with_read_csv(df)
65
49
 
66
50
 
67
51
  def _rename_columns(df: pd.DataFrame) -> pd.DataFrame:
@@ -101,6 +85,32 @@ def _rename_columns(df: pd.DataFrame) -> pd.DataFrame:
101
85
  def _process_df(raw_df: pd.DataFrame) -> pd.DataFrame:
102
86
  df = raw_df.copy()
103
87
 
88
+ # Clean and reformat the DataFrame columns
89
+ df.columns = (df.columns
90
+ .str.replace("SctyQtn.", "")
91
+ .str.replace("asset.AsstSummry.", "")
92
+ ) # fmt: skip
93
+ df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
94
+
95
+ df = _rename_columns(df)
96
+
97
+ # Convert maturity codes to datetime and drop rows with missing values
98
+ df["ExpirationDate"] = pd.to_datetime(df["ExpirationDate"], errors="coerce")
99
+ df.dropna(subset=["ExpirationDate"], inplace=True)
100
+
101
+ # Sort the DataFrame by maturity code and reset the index
102
+ df.sort_values("ExpirationDate", inplace=True, ignore_index=True)
103
+
104
+ # Get currante date
105
+ today = pd.Timestamp.now().normalize()
106
+ df["TradeDate"] = today
107
+
108
+ # Get current date and time
109
+ now = pd.Timestamp.now().round("s")
110
+ # Subtract 15 minutes from the current time to account for API delay
111
+ trade_ts = now - pd.Timedelta(minutes=15)
112
+ df["TradeTime"] = trade_ts
113
+
104
114
  df["BDaysToExp"] = bday.count_bdays(df["TradeDate"], df["ExpirationDate"])
105
115
 
106
116
  df["DaysToExp"] = (df["ExpirationDate"] - df["TradeDate"]).dt.days
@@ -156,10 +166,9 @@ def fetch_intraday_df(future_code: str) -> pd.DataFrame:
156
166
  Returns:
157
167
  pd.DataFrame: A Pandas pd.DataFrame containing the latest DI futures data.
158
168
  """
159
- raw_df = _fetch_raw_df(future_code)
169
+ raw_df = _fetch_b3_df(future_code)
160
170
  if raw_df.empty:
161
171
  return raw_df
162
- df = _rename_columns(raw_df)
163
- df = _process_df(df)
172
+ df = _process_df(raw_df)
164
173
  df = _select_and_reorder_columns(df)
165
174
  return df
@@ -43,7 +43,7 @@ def _get_file_from_url(trade_date: pd.Timestamp, source_type: str) -> io.BytesIO
43
43
  else:
44
44
  raise ValueError("Invalid source type. Must be either 'PR' or 'SPR'.")
45
45
 
46
- response = requests.get(url)
46
+ response = requests.get(url, timeout=10)
47
47
 
48
48
  # File will be considered invalid if it is too small
49
49
  if response.status_code != 200 or len(response.content) < 1024:
@@ -74,9 +74,15 @@ def _extract_xml_from_zip(zip_file: io.BytesIO) -> io.BytesIO:
74
74
 
75
75
  def _extract_data_from_xml(xml_file: io.BytesIO, asset_code: str) -> list[dict]:
76
76
  parser = etree.XMLParser(
77
- ns_clean=True, remove_blank_text=True, remove_comments=True, recover=True
77
+ ns_clean=True,
78
+ remove_blank_text=True,
79
+ remove_comments=True,
80
+ recover=True,
81
+ resolve_entities=False,
82
+ no_network=True,
83
+ load_dtd=False, # Disable DTD loading
78
84
  )
79
- tree = etree.parse(xml_file, parser)
85
+ tree = etree.parse(xml_file, parser=parser)
80
86
 
81
87
  # XPath para encontrar elementos cujo texto começa com código do ativo: DI1, FRC...
82
88
  namespaces = {"ns": "urn:bvmf.217.01.xsd"}
@@ -302,7 +302,7 @@ def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
302
302
  """
303
303
  df = da.fetch_asset(asset_code="DI1", reference_date=reference_date)
304
304
  if "CurrentRate" in df.columns:
305
- df = df.rename(columns={"CurrentRate": "NIR"})
305
+ df = df.rename(columns={"CurrentRate": "NSR_DI"})
306
306
  keep_cols = [
307
307
  "TradeDate",
308
308
  "TradeTime",
@@ -311,15 +311,21 @@ def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
311
311
  "BDaysToExp",
312
312
  "CurrentAskRate",
313
313
  "CurrentBidRate",
314
- "NIR",
314
+ "NSR_DI",
315
315
  ]
316
316
  elif "SettlementRate" in df.columns:
317
- df = df.rename(columns={"SettlementRate": "NIR"})
318
- keep_cols = ["TradeDate", "TickerSymbol", "ExpirationDate", "BDaysToExp", "NIR"]
317
+ df = df.rename(columns={"SettlementRate": "NSR_DI"})
318
+ keep_cols = [
319
+ "TradeDate",
320
+ "TickerSymbol",
321
+ "ExpirationDate",
322
+ "BDaysToExp",
323
+ "NSR_DI",
324
+ ]
319
325
  else:
320
326
  raise ValueError("NIR data not found in the DataFrame.")
321
327
 
322
- df = df[keep_cols].dropna(subset=["NIR"])
328
+ df = df[keep_cols].dropna(subset=["NSR_DI"])
323
329
 
324
330
  # Add DI spreads for prefixed bonds (LTN) and adjust NIR
325
331
  today = pd.Timestamp.today().normalize()
@@ -334,7 +340,7 @@ def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
334
340
  df_pre.drop(columns=["BondType"], inplace=True)
335
341
 
336
342
  df = pd.merge_asof(df, df_pre, left_on="ExpirationDate", right_on="MaturityDate")
337
- df["NIRAdj"] = df["NIR"] + df["DISpread"]
343
+ df["NSR_PRE"] = df["NSR_DI"] + df["DISpread"]
338
344
 
339
345
  return df
340
346
 
@@ -346,46 +352,57 @@ def breakeven_inflation(
346
352
  ytm_rates: pd.Series,
347
353
  ) -> pd.DataFrame:
348
354
  """
349
- Calculate the Breakeven Inflation (BEI) for NTN-B bonds based on nominal and real
355
+ Calculate the Breakeven Inflation (INF) for NTN-B bonds based on nominal and real
350
356
  interest rates.
351
357
 
352
358
  Args:
353
- reference_date (str | pd.Timestamp): The reference date for fetching data and
354
- calculations.
355
- settlement_date (str | pd.Timestamp): The settlement date for the bonds.
356
- maturity_dates (pd.Series): Series of maturity dates for the bonds.
357
- ytm_rates (pd.Series): Series of Yield to Maturity (YTM) rates corresponding to
358
- the maturity dates.
359
+ reference_date (str or pd.Timestamp): The reference date for fetching data and
360
+ performing calculations.
361
+ settlement_date (str or pd.Timestamp): The settlement date for the bonds.
362
+ maturity_dates (pd.Series): A series of maturity dates for the bonds.
363
+ ytm_rates (pd.Series): A series of Yield to Maturity (YTM) rates corresponding
364
+ to the maturity dates.
359
365
 
360
366
  Returns:
361
- pd.DataFrame: DataFrame containing the maturity dates, real interest rates
362
- (RIR), nominal interest rates (NIR) and breakeven inflation rates (BEI).
367
+ pd.DataFrame: DataFrame containing the maturity dates, business days to maturity
368
+ (BDays), Yield to Maturity (YTM), real spot rates (RSR), nominal spot rates
369
+ (NSR_DI and NSR_PRE), and breakeven inflation rates (INF_DI and INF_PRE).
363
370
  """
364
371
  # Normalize input dates
365
372
  reference_date = dv.normalize_date(reference_date)
366
373
  settlement_date = dv.normalize_date(settlement_date)
367
374
 
368
- # Fetch Nominal Interest Rate (NIR) data
375
+ # Fetch Nominal Spot Rate (NSR) data
369
376
  df_nir = _get_nir_df(reference_date)
370
377
  known_bdays = df_nir["BDaysToExp"].to_list()
371
- known_rates = df_nir["NIR"].to_list()
378
+ known_rates = df_nir["NSR_DI"].to_list()
372
379
 
373
- # Calculate Real Interest Rate (RIR)
380
+ # Calculate Real Spot Rate (RSR)
374
381
  df_rir = spot_rates(settlement_date, maturity_dates, ytm_rates)
375
- df_rir = df_rir.rename(columns={"SpotRate": "RIR"})
382
+ df_rir = df_rir.rename(columns={"SpotRate": "RSR"})
376
383
  df_rir["BDays"] = bday.count_bdays(reference_date, df_rir["MaturityDate"])
377
- df_rir["NIR"] = df_rir["BDays"].apply(
384
+ df_rir["NSR_DI"] = df_rir["BDays"].apply(
378
385
  lambda x: ip.find_and_interpolate_flat_forward(x, known_bdays, known_rates)
379
386
  )
380
387
 
381
- # Calculate Breakeven Inflation (BEI)
382
- df_rir["BEI"] = ((df_rir["NIR"] + 1) / (df_rir["RIR"] + 1)) - 1
388
+ # Calculate Breakeven Inflation (INF)
389
+ df_rir["INF_DI"] = ((df_rir["NSR_DI"] + 1) / (df_rir["RSR"] + 1)) - 1
383
390
 
384
391
  # Adjust BEI for DI spread in prefixed bonds
385
- known_rates = df_nir["NIRAdj"].to_list()
386
- df_rir["NIRAdj"] = df_rir["BDays"].apply(
392
+ known_rates = df_nir["NSR_PRE"].to_list()
393
+ df_rir["NSR_PRE"] = df_rir["BDays"].apply(
387
394
  lambda x: ip.find_and_interpolate_flat_forward(x, known_bdays, known_rates)
388
395
  )
389
- df_rir["BEIAdj"] = ((df_rir["NIRAdj"] + 1) / (df_rir["RIR"] + 1)) - 1
390
-
391
- return df_rir.drop(columns=["BDays", "NIR", "NIRAdj"])
396
+ df_rir["INF_PRE"] = ((df_rir["NSR_PRE"] + 1) / (df_rir["RSR"] + 1)) - 1
397
+
398
+ order_cols = [
399
+ "MaturityDate",
400
+ "BDays",
401
+ "YTM",
402
+ "RSR",
403
+ "NSR_DI",
404
+ "NSR_PRE",
405
+ "INF_DI",
406
+ "INF_PRE",
407
+ ]
408
+ return df_rir[order_cols].copy()
@@ -54,7 +54,7 @@ def fetch_current_month_ipca_projection() -> IndicatorProjection:
54
54
  """
55
55
  # Define the URL and get the data
56
56
  url = "https://www.anbima.com.br/informacoes/indicadores/arqs/indicadores.xls"
57
- response = requests.get(url)
57
+ response = requests.get(url, timeout=10)
58
58
  excel_data = io.BytesIO(response.content)
59
59
 
60
60
  # Load the workbook and select the first sheet
@@ -32,7 +32,7 @@ dependencies = [
32
32
  "requests>=2.31.0",
33
33
  ]
34
34
  dynamic = []
35
- version = "0.8.1"
35
+ version = "0.8.2"
36
36
 
37
37
  [project.license]
38
38
  file = "LICENSE"
@@ -1 +0,0 @@
1
- __version__ = "0.8.1"
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes