PYield 0.8.3__tar.gz → 0.8.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 (32) hide show
  1. {pyield-0.8.3 → pyield-0.8.4}/PKG-INFO +1 -1
  2. pyield-0.8.4/pyield/__about__.py +1 -0
  3. {pyield-0.8.3 → pyield-0.8.4}/pyield/anbima.py +35 -18
  4. {pyield-0.8.3 → pyield-0.8.4}/pyield/data_access.py +6 -6
  5. {pyield-0.8.3 → pyield-0.8.4}/pyield/ntnb.py +42 -32
  6. {pyield-0.8.3 → pyield-0.8.4}/pyield/spreads.py +1 -1
  7. {pyield-0.8.3 → pyield-0.8.4}/pyproject.toml +4 -1
  8. pyield-0.8.3/pyield/__about__.py +0 -1
  9. {pyield-0.8.3 → pyield-0.8.4}/LICENSE +0 -0
  10. {pyield-0.8.3 → pyield-0.8.4}/README.md +0 -0
  11. {pyield-0.8.3 → pyield-0.8.4}/pyield/__init__.py +0 -0
  12. {pyield-0.8.3 → pyield-0.8.4}/pyield/bday.py +0 -0
  13. {pyield-0.8.3 → pyield-0.8.4}/pyield/date_validator.py +0 -0
  14. {pyield-0.8.3 → pyield-0.8.4}/pyield/futures/__init__.py +0 -0
  15. {pyield-0.8.3 → pyield-0.8.4}/pyield/futures/common.py +0 -0
  16. {pyield-0.8.3 → pyield-0.8.4}/pyield/futures/historical.py +0 -0
  17. {pyield-0.8.3 → pyield-0.8.4}/pyield/futures/intraday.py +0 -0
  18. {pyield-0.8.3 → pyield-0.8.4}/pyield/futures/xml.py +0 -0
  19. {pyield-0.8.3 → pyield-0.8.4}/pyield/holidays/__init__.py +0 -0
  20. {pyield-0.8.3 → pyield-0.8.4}/pyield/holidays/br_holidays_new.txt +0 -0
  21. {pyield-0.8.3 → pyield-0.8.4}/pyield/holidays/br_holidays_old.txt +0 -0
  22. {pyield-0.8.3 → pyield-0.8.4}/pyield/holidays/core.py +0 -0
  23. {pyield-0.8.3 → pyield-0.8.4}/pyield/indicators.py +0 -0
  24. {pyield-0.8.3 → pyield-0.8.4}/pyield/interpolator.py +0 -0
  25. {pyield-0.8.3 → pyield-0.8.4}/pyield/projections.py +0 -0
  26. {pyield-0.8.3 → pyield-0.8.4}/pyield/py.typed +0 -0
  27. {pyield-0.8.3 → pyield-0.8.4}/tests/__init__.py +0 -0
  28. {pyield-0.8.3 → pyield-0.8.4}/tests/data/SPRD231222.zip +0 -0
  29. {pyield-0.8.3 → pyield-0.8.4}/tests/data/SPRD240426.zip +0 -0
  30. {pyield-0.8.3 → pyield-0.8.4}/tests/test_bday.py +0 -0
  31. {pyield-0.8.3 → pyield-0.8.4}/tests/test_futures.py +0 -0
  32. {pyield-0.8.3 → pyield-0.8.4}/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.3
3
+ Version: 0.8.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>
@@ -0,0 +1 @@
1
+ __version__ = "0.8.4"
@@ -12,6 +12,7 @@ def _get_file_content(reference_date: pd.Timestamp, headers: dict = None) -> str
12
12
  headers = headers or {} # Default empty headers
13
13
  url_date = reference_date.strftime("%y%m%d")
14
14
  member_url = f"{ANBIMA_MEMBER_URL}ms{url_date}.txt"
15
+ # Non member url example: https://www.anbima.com.br/informacoes/merc-sec/arqs/ms240614.txt
15
16
  non_member_url = f"{ANBIMA_NON_MEMBER_URL}ms{url_date}.txt"
16
17
 
17
18
  # Tries to access the member URL first
@@ -49,40 +50,37 @@ def _read_raw_df(file_content: str) -> pd.DataFrame:
49
50
 
50
51
  def _process_raw_df(df_raw: pd.DataFrame) -> pd.DataFrame:
51
52
  """
52
- Process raw data from ANBIMA by filtering selected columns, renaming them and
53
- adjusting data formats.
53
+ Process raw data from ANBIMA by renaming columns and adjusting data formats.
54
54
 
55
55
  Parameters:
56
- - df (pd.DataFrame): Raw data DataFrame to process.
56
+ df (pd.DataFrame): Raw DataFrame to process.
57
57
 
58
58
  Returns:
59
- - pd.DataFrame: Processed DataFrame.
59
+ pd.DataFrame: Processed DataFrame.
60
60
  """
61
61
  # Filter selected columns and rename them
62
62
  selected_columns_dict = {
63
63
  "Titulo": "BondType",
64
64
  "Data Referencia": "ReferenceDate",
65
- # "Codigo SELIC": "SelicCode",
66
- # "Data Base/Emissao": "IssueBaseDate",
65
+ "Codigo SELIC": "SelicCode",
66
+ "Data Base/Emissao": "IssueBaseDate",
67
67
  "Data Vencimento": "MaturityDate",
68
68
  "Tx. Compra": "BidRate",
69
69
  "Tx. Venda": "AskRate",
70
70
  "Tx. Indicativas": "IndicativeRate",
71
71
  "PU": "Price",
72
- # "Desvio padrao": "StdDev",
73
- # "Interv. Ind. Inf. (D0)",
74
- # "Interv. Ind. Sup. (D0)",
75
- # "Interv. Ind. Inf. (D+1)",
76
- # "Interv. Ind. Sup. (D+1)",
77
- # "Criterio": "Criteria",
72
+ "Desvio padrao": "StdDev",
73
+ "Interv. Ind. Inf. (D0)": "LowerBoundRateD0",
74
+ "Interv. Ind. Sup. (D0)": "UpperBoundRateD0",
75
+ "Interv. Ind. Inf. (D+1)": "LowerBoundRateD1",
76
+ "Interv. Ind. Sup. (D+1)": "UpperBoundRateD1",
77
+ "Criterio": "Criteria",
78
78
  }
79
- select_columns = list(selected_columns_dict.keys())
80
- df = df_raw[select_columns].copy()
81
- df = df.rename(columns=selected_columns_dict)
79
+ df = df_raw.rename(columns=selected_columns_dict)
82
80
 
83
81
  # Remove percentage from rates
84
- rate_cols = ["BidRate", "AskRate", "IndicativeRate"]
85
- df[rate_cols] = df[rate_cols] / 100
82
+ rate_cols = [col for col in df.columns if "Rate" in col]
83
+ df[rate_cols] = (df[rate_cols] / 100).round(6)
86
84
 
87
85
  df["ReferenceDate"] = pd.to_datetime(df["ReferenceDate"], format="%Y%m%d")
88
86
  df["MaturityDate"] = pd.to_datetime(df["MaturityDate"], format="%Y%m%d")
@@ -90,7 +88,7 @@ def _process_raw_df(df_raw: pd.DataFrame) -> pd.DataFrame:
90
88
  return df.sort_values(["BondType", "MaturityDate"], ignore_index=True)
91
89
 
92
90
 
93
- def fetch_bonds(reference_date: pd.Timestamp, headers: dict = None) -> pd.DataFrame:
91
+ def fetch_data(reference_date: pd.Timestamp, headers: dict = None) -> pd.DataFrame:
94
92
  """
95
93
  Fetches indicative treasury rates from ANBIMA for a specified reference date.
96
94
 
@@ -114,3 +112,22 @@ def fetch_bonds(reference_date: pd.Timestamp, headers: dict = None) -> pd.DataFr
114
112
  df = _read_raw_df(file_content)
115
113
 
116
114
  return _process_raw_df(df)
115
+
116
+
117
+ def fetch_rates(reference_date: pd.Timestamp, headers: dict = None) -> pd.DataFrame:
118
+ """
119
+ Fetches indicative treasury rates from ANBIMA for a specified reference date.
120
+
121
+ This function retrieves the indicative rates for Brazilian treasury securities
122
+ from ANBIMA, processing them into a structured pandas DataFrame.
123
+
124
+ Parameters:
125
+ reference_date (pd.Timestamp): The date for which to fetch the indicative rates.
126
+
127
+ Returns:
128
+ pd.DataFrame: A DataFrame containing the processed indicative rates for the
129
+ given reference date.
130
+
131
+ """
132
+ df = fetch_data(reference_date, headers)
133
+ return df[["BondType", "MaturityDate", "IndicativeRate"]].copy()
@@ -17,7 +17,7 @@ def fetch_asset(
17
17
  Args:
18
18
  asset_code (str): The asset code identifying the type of financial asset.
19
19
  Supported options:
20
- - "TRB": Treasury bonds (indicative rates from ANBIMA).
20
+ - "TRB": Brazilian treasury bonds (indicative rates from ANBIMA).
21
21
  - "LTN", "LFT", "NTN-F", "NTN-B": Specific types of Brazilian treasury bonds
22
22
  (indicative rates from ANBIMA).
23
23
  - "DI1": One-day Interbank Deposit Futures (Futuro de DI) from B3.
@@ -42,7 +42,7 @@ def fetch_asset(
42
42
  ValueError: If the asset code is not recognized or supported.
43
43
 
44
44
  Examples:
45
- >>> fetch_asset("TRB", "31-05-2024")
45
+ >>> fetch_asset("LTN", "31-05-2024")
46
46
  >>> fetch_asset("DI1", "31-05-2024")
47
47
  """
48
48
  SUPPORTED_BONDS = ["LTN", "LFT", "NTN-F", "NTN-B"]
@@ -55,13 +55,13 @@ def fetch_asset(
55
55
  return ft.fetch_intraday_df(future_code=asset_code.upper())
56
56
 
57
57
  if asset_code.upper() == "TRB":
58
- return an.fetch_bonds(reference_date=normalized_date)
58
+ return an.fetch_data(reference_date=normalized_date)
59
59
 
60
60
  if asset_code.upper() in SUPPORTED_BONDS:
61
- df_bond = an.fetch_bonds(reference_date=normalized_date)
61
+ df_bond = an.fetch_data(reference_date=normalized_date)
62
62
  df_bond.query(f"BondType == '{asset_code.upper()}'", inplace=True)
63
- # Return the DataFrame sorted by maturity date
64
- return df_bond.sort_values("MaturityDate", ignore_index=True)
63
+ # Return the DataFrame with the index reset
64
+ return df_bond.reset_index(drop=True)
65
65
 
66
66
  if asset_code.upper() in SUPPORTED_FUTURES:
67
67
  return ft.fetch_historical_df(
@@ -23,7 +23,7 @@ def anbima_data(reference_date: str | pd.Timestamp) -> pd.DataFrame:
23
23
  return da.fetch_asset(asset_code="NTN-B", reference_date=reference_date)
24
24
 
25
25
 
26
- def anbima_rates(reference_date: str | pd.Timestamp) -> pd.DataFrame:
26
+ def ytm_rates(reference_date: str | pd.Timestamp) -> pd.DataFrame:
27
27
  """
28
28
  Fetch NTN-B Anbima indicative rates for the given reference date.
29
29
 
@@ -33,10 +33,12 @@ def anbima_rates(reference_date: str | pd.Timestamp) -> pd.DataFrame:
33
33
  Returns:
34
34
  pd.DataFrame: A DataFrame containing the maturity dates and corresponding rates.
35
35
  """
36
- return anbima_data(reference_date)[["MaturityDate", "IndicativeRate"]]
36
+ df = anbima_data(reference_date)[["MaturityDate", "IndicativeRate"]]
37
+ # Rename IndicativeRate to YTM for consistency
38
+ return df.rename(columns={"IndicativeRate": "YTM"})
37
39
 
38
40
 
39
- def truncate(value, decimal_places):
41
+ def _truncate(value, decimal_places):
40
42
  """
41
43
  Truncate a float or a Pandas Series to the specified decimal place.
42
44
 
@@ -178,16 +180,16 @@ def quotation(
178
180
  cf = np.where(payment_dates == maturity_date, FINAL_PMT, INTER_PMT)
179
181
 
180
182
  # Calculate the number of periods truncated to 14 decimal places
181
- n = truncate(bdays / 252, 14)
183
+ n = _truncate(bdays / 252, 14)
182
184
 
183
185
  # Calculate the present value of each cash flow (DCF) rounded to 10 decimal places
184
186
  dcf = (cf / (1 + discount_rate) ** n).round(10)
185
187
 
186
188
  # Return the quotation (the dcf sum) truncated to 4 decimal places
187
- return truncate(dcf.sum(), 4)
189
+ return _truncate(dcf.sum(), 4)
188
190
 
189
191
 
190
- def prepare_interpolation_data(
192
+ def _prepare_interpolation_data(
191
193
  reference_date: pd.Timestamp, maturity_dates: pd.Series, rates: pd.Series
192
194
  ) -> tuple:
193
195
  """
@@ -242,7 +244,7 @@ def spot_rates(
242
244
  settlement_date = dv.normalize_date(settlement_date)
243
245
 
244
246
  # Prepare the data for interpolation
245
- ordered_bdays, ordered_ytms = prepare_interpolation_data(
247
+ ordered_bdays, ordered_ytms = _prepare_interpolation_data(
246
248
  settlement_date, maturity_dates, ytm_rates
247
249
  )
248
250
  # Generate coupon dates and initialize the main DataFrame
@@ -253,7 +255,7 @@ def spot_rates(
253
255
  # Add auxiliary columns for calculations
254
256
  df["BDays"] = bday.count_bdays(settlement_date, df["MaturityDate"])
255
257
  df["YTM"] = 0.0
256
- df["SpotRate"] = 0.0
258
+ df["RSR"] = 0.0
257
259
 
258
260
  # Main loop to calculate spot rates
259
261
  for index in df.index:
@@ -266,7 +268,7 @@ def spot_rates(
266
268
 
267
269
  # Create the Series that will be used to calculate the discounted cash flows
268
270
  cfs = pd.Series(COUPON, index=dfl.index)
269
- spot_rates = dfl["SpotRate"]
271
+ spot_rates = dfl["RSR"]
270
272
  periods = dfl["BDays"] / 252
271
273
 
272
274
  # Calculate the present value of the cash flows (discounted cash flows)
@@ -279,7 +281,7 @@ def spot_rates(
279
281
  spot_rate = ((COUPON + 1) / (ntnb_quotation - dcfs.sum())) ** (252 / bd) - 1
280
282
 
281
283
  # Update DataFrame with calculated values
282
- df.at[index, "SpotRate"] = spot_rate
284
+ df.at[index, "RSR"] = spot_rate
283
285
  df.at[index, "YTM"] = ytm
284
286
 
285
287
  # Drop the BDays column, remove intermediate cupon dates and reset the index.
@@ -290,7 +292,7 @@ def spot_rates(
290
292
  )
291
293
 
292
294
 
293
- def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
295
+ def _get_nsr_df(reference_date: pd.Timestamp) -> pd.DataFrame:
294
296
  """
295
297
  Fetch the Nominal Interest Rate (NIR) data for NTN-B bonds.
296
298
 
@@ -345,14 +347,14 @@ def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
345
347
  return df
346
348
 
347
349
 
348
- def breakeven_inflation(
350
+ def bei_rates(
349
351
  reference_date: str | pd.Timestamp,
350
352
  settlement_date: str | pd.Timestamp,
351
353
  maturity_dates: pd.Series,
352
354
  ytm_rates: pd.Series,
353
355
  ) -> pd.DataFrame:
354
356
  """
355
- Calculate the Breakeven Inflation (INF) for NTN-B bonds based on nominal and real
357
+ Calculate the Breakeven Inflation (BEI) for NTN-B bonds based on nominal and real
356
358
  interest rates.
357
359
 
358
360
  Args:
@@ -364,45 +366,53 @@ def breakeven_inflation(
364
366
  to the maturity dates.
365
367
 
366
368
  Returns:
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).
369
+ pd.DataFrame: DataFrame containing the breakeven inflation rates.
370
+
371
+ Returned columns:
372
+ - MaturityDate: Maturity date of the bond.
373
+ - BDays: Number of business days from the settlement date to the maturity.
374
+ - YTM: Yield to Maturity rate for the bond.
375
+ - RSR: Real Spot Rate for the bond.
376
+ - NSR_DI: Nominal Spot Rate for the bond.
377
+ - NSR_PRE: Nominal Spot Rate for the bond with DI spread.
378
+ - BIR_DI: Breakeven Inflation Rate for the bond.
379
+ - BIR_PRE: Breakeven Inflation Rate for the bond adjusted for DI spread.
370
380
  """
371
381
  # Normalize input dates
372
382
  reference_date = dv.normalize_date(reference_date)
373
383
  settlement_date = dv.normalize_date(settlement_date)
374
384
 
375
385
  # Fetch Nominal Spot Rate (NSR) data
376
- df_nir = _get_nir_df(reference_date)
377
- known_bdays = df_nir["BDaysToExp"].to_list()
378
- known_rates = df_nir["NSR_DI"].to_list()
386
+ df_nsr = _get_nsr_df(reference_date)
387
+ known_bdays = df_nsr["BDaysToExp"].to_list()
388
+ known_rates = df_nsr["NSR_DI"].to_list()
379
389
 
380
390
  # Calculate Real Spot Rate (RSR)
381
- df_rir = spot_rates(settlement_date, maturity_dates, ytm_rates)
382
- df_rir = df_rir.rename(columns={"SpotRate": "RSR"})
383
- df_rir["BDays"] = bday.count_bdays(reference_date, df_rir["MaturityDate"])
384
- df_rir["NSR_DI"] = df_rir["BDays"].apply(
391
+ df = spot_rates(settlement_date, maturity_dates, ytm_rates)
392
+ df = df.rename(columns={"RSR": "RSR"})
393
+ df["BDays"] = bday.count_bdays(reference_date, df["MaturityDate"])
394
+ df["NSR_DI"] = df["BDays"].apply(
385
395
  lambda x: ip.find_and_interpolate_flat_forward(x, known_bdays, known_rates)
386
396
  )
387
397
 
388
- # Calculate Breakeven Inflation (INF)
389
- df_rir["INF_DI"] = ((df_rir["NSR_DI"] + 1) / (df_rir["RSR"] + 1)) - 1
398
+ # Calculate Breakeven Inflation Rate (BIR)
399
+ df["BIR_DI"] = ((df["NSR_DI"] + 1) / (df["RSR"] + 1)) - 1
390
400
 
391
401
  # Adjust BEI for DI spread in prefixed bonds
392
- known_rates = df_nir["NSR_PRE"].to_list()
393
- df_rir["NSR_PRE"] = df_rir["BDays"].apply(
402
+ known_rates = df_nsr["NSR_PRE"].to_list()
403
+ df["NSR_PRE"] = df["BDays"].apply(
394
404
  lambda x: ip.find_and_interpolate_flat_forward(x, known_bdays, known_rates)
395
405
  )
396
- df_rir["INF_PRE"] = ((df_rir["NSR_PRE"] + 1) / (df_rir["RSR"] + 1)) - 1
406
+ df["BIR_PRE"] = ((df["NSR_PRE"] + 1) / (df["RSR"] + 1)) - 1
397
407
 
398
- order_cols = [
408
+ cols_reordered = [
399
409
  "MaturityDate",
400
410
  "BDays",
401
411
  "YTM",
402
412
  "RSR",
403
413
  "NSR_DI",
404
414
  "NSR_PRE",
405
- "INF_DI",
406
- "INF_PRE",
415
+ "BIR_DI",
416
+ "BIR_PRE",
407
417
  ]
408
- return df_rir[order_cols].copy()
418
+ return df[cols_reordered].copy()
@@ -38,7 +38,7 @@ def calculate_di_spreads(reference_date: pd.Timestamp) -> pd.DataFrame:
38
38
  df_di["MaturityDate"] = df_di["MaturityDate"].dt.to_period("M").dt.to_timestamp()
39
39
 
40
40
  # Fetch bond rates, filtering for LTN and NTN-F types
41
- df_anbima = an.fetch_bonds(reference_date, False)
41
+ df_anbima = an.fetch_data(reference_date, False)
42
42
  df_anbima.query("BondType in ['LTN', 'NTN-F']", inplace=True)
43
43
 
44
44
  # Merge bond and DI rates by maturity date to calculate spreads
@@ -32,7 +32,7 @@ dependencies = [
32
32
  "requests>=2.31.0",
33
33
  ]
34
34
  dynamic = []
35
- version = "0.8.3"
35
+ version = "0.8.4"
36
36
 
37
37
  [project.license]
38
38
  file = "LICENSE"
@@ -52,6 +52,9 @@ dev = [
52
52
  "ipykernel",
53
53
  "pytest",
54
54
  "mypy",
55
+ "mkdocs",
56
+ "mkdocs-material",
57
+ "mkdocstrings[python]",
55
58
  ]
56
59
 
57
60
  [tool.ruff]
@@ -1 +0,0 @@
1
- __version__ = "0.8.3"
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